//
//  ServiceFriendFeed.m
//  Afficheur
//
//  Created by kichi on 10/01/03.
//  Copyright 2010 Katsuhiko Ichinose. All rights reserved.
//

#import "ServiceFriendFeed.h"
#import "ServiceFriendFeedKey.h"
#import "AfficheurController.h"
#import "AfficheurPreferences.h"
#import "AfficheurGrowl.h"
#import "NSStringOgreKit.h"
#import <OgreKit/OgreKit.h>
#import <JSON/JSON.h>
#import "XMPPStream.h"
#import "NSXMLElementAdditions.h"
#import "i18n.h"

@implementation ServiceFriendFeed

static NSString* REQUEST_TOKEN_PATH = @"https://friendfeed.com/account/oauth/request_token";
static NSString* IA_ACCESS_TOKEN_PATH = @"https://friendfeed.com/account/oauth/ia_access_token";
static NSString* AUTHORIZE_PATH = @"https://friendfeed.com/account/oauth/authorize";
static NSString* ACCESS_TOKEN_PATH = @"https://friendfeed.com/account/oauth/access_token";

- (id)init
{
	self = (ServiceFriendFeed*)[super init];
	if (self)
	{
		_title = nil;
		_first = YES;
		_request_token = nil;
		_request_token_secret = nil;
		_access_token = nil;
		_access_token_secret = nil;
	}
	return self;
}


- (void)dealloc
{
	[_title release];
	[_userProfile release];
	if (_request_token)
	{
		[_request_token release];
	}
	if (_request_token_secret)
	{
		[_request_token_secret release];
	}
	if (_access_token)
	{
		[_access_token release];
	}
	if (_access_token_secret)
	{
		[_access_token_secret release];
	}
	[super dealloc];
}

- (id)init:(NSString *)service
WithController:(AfficheurController *)controller
preferences:(AfficheurPreferences *)preferences
{
	self = [super init:service
		WithController:controller
		   preferences:preferences];
	if (self)
	{
		_userProfile = [[NSMutableDictionary alloc] init];
		if ([service isEqualToString:FriendFeed])
		{
			_notify = GrowlAfficheurFriendFeedNotify;
			_notifyComment = GrowlAfficheurFriendFeedCommentNotify;
			_notifyReply = GrowlAfficheurFriendFeedReplyNotify;
			_notifyError = GrowlAfficheurFriendFeedErrorNotify;
		}
	}
	return self;
}
#if 0
- (void)setupXMPP:(ServiceXMPP *)xmpp
{
	[xmpp addService:FriendFeed withObject:self];
	if ([_preferences accountFriendFeedUseXMPP])
	{
		[xmpp addXMPP:FriendFeed
			  withJid:[_preferences accountFriendFeedJidXMPP]
				 pass:[_preferences accountFriendFeedPassXMPP]
			   server:[_preferences accountFriendFeedServerXMPP]
				 port:[_preferences accountFriendFeedPortXMPP]
			  offline:[_preferences accountFriendFeedOfflineXMPP]];
	}
	else
	{
		[xmpp removeXMPP:FriendFeed];
	}
}
#endif
- (void)setRequestToken:(NSString *)requestToken
{
	if (_request_token)
	{
		[_request_token release];
	}
	_request_token = [requestToken copy];
}

- (void)setRequestTokenSecret:(NSString *)requestTokenSecret
{
	if (_request_token_secret)
	{
		[_request_token_secret release];
	}
	_request_token_secret = [requestTokenSecret copy];
}

- (void)setAccessToken:(NSString *)accessToken
{
	if (_access_token)
	{
		[_access_token release];
	}
	_access_token = [accessToken copy];
}

- (void)setAccessTokenSecret:(NSString *)accessTokenSecret
{
	if (_access_token_secret)
	{
		[_access_token_secret release];
	}
	_access_token_secret = [accessTokenSecret copy];
}

- (NSString *)accessToken
{
	return [[_access_token copy] autorelease];
}

- (NSString *)accessTokenSecret
{
	return [[_access_token_secret copy] autorelease];
}

- (FriendFeedAPI *)createAPI:(NSDictionary *)dic
{
	FriendFeedAPI *api = [[[FriendFeedAPI alloc] initWithDictionary:dic] autorelease];
	if (api)
	{
		[api setConsumerKey:CONSUMER_KEY];
		[api setConsumerSecret:CONSUMER_SECRET];
		[api setRequestTokenPath:REQUEST_TOKEN_PATH];
		[api setAuthorizePath:AUTHORIZE_PATH];
		[api setAccessTokenPath:ACCESS_TOKEN_PATH];
	}
	return api;
}

- (NSString *)authorize
{
	LOG2(@"[%@ authorize]", [self className]);
	NSString *authorize = nil;
	@try
	{
		NSString *user = [_preferences accountFriendFeedUser];
		NSString *pass = [_preferences accountFriendFeedPass];
		NSString *key = [_preferences accountFriendFeedOAuthKey];
		NSString *secret = [_preferences accountFriendFeedOAuthSecret];
		NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
							 user, FriendFeedUser,
							 pass, FriendFeedPass,
							 key, FriendFeedKey,
							 secret, FriendFeedSecret,
							 nil];
		FriendFeedAPI *api = [self createAPI:dic];
		if ([api ia_access_token:IA_ACCESS_TOKEN_PATH])
		{
			[self setAccessToken:[api accessToken]];
			[self setAccessTokenSecret:[api accessTokenSecret]];
			authorize = [NSString stringWithFormat:
						 @"oauth_token=%@&oauth_token_secret=%@",
						 [api accessToken], [api accessTokenSecret]];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ authorize] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return authorize;
}

- (BOOL)access_token
{
	LOG(@"[%@ access_token]", [self className]);
	BOOL access_token = NO;
	@try
	{
		OAuth *oAuth = [[OAuth alloc] init];
		[oAuth setConsumerKey:CONSUMER_KEY];
		[oAuth setConsumerSecret:CONSUMER_SECRET];
		[oAuth setRequestToken:_request_token];
		[oAuth setRequestTokenSecret:_request_token_secret];
		[oAuth setRequestTokenPath:REQUEST_TOKEN_PATH];
		[oAuth setAuthorizePath:AUTHORIZE_PATH];
		[oAuth setAccessTokenPath:ACCESS_TOKEN_PATH];
		if ([oAuth access_token])
		{
			[self setAccessToken:[oAuth accessToken]];
			[self setAccessTokenSecret:[oAuth accessTokenSecret]];
			access_token = YES;
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ access_token] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return access_token;
}


- (void)setupRetrieveDate:(NSDate *)date
{
	[self setupRetrieveDateTimeline:date];
}

- (void)setupRetrieveDateTimeline:(NSDate *)date
{
	@synchronized(self)
	{
		if (_dateTimeline)
		{
			[_dateTimeline release];
		}
		if (date)
		{
			_dateTimeline = [[NSDate alloc] initWithTimeInterval:0 sinceDate:date];
		}
		else
		{
			_dateTimeline = nil;
		}
	}
}

- (BOOL)checkRetrieveTimeline:(NSDate *)now
{
	BOOL check = NO;
	@synchronized(self)
	{
		NSTimeInterval interval = [_preferences accountFriendFeedIntervalTimeline];
		if (_dateTimeline && (interval > 0))
		{
			if (![_dateTimeline isKindOfClass:[NSDate class]])
			{
				LOG(@"[%@(%@) checkRetrieve] %@", [self className], _service, [_dateTimeline className]);
			}
			else
			{
				NSDate *aDate = [_dateTimeline addTimeInterval:interval];
				if ([now compare:aDate] != NSOrderedAscending)
				{
					check = YES;
				}
			}
		}
	}
	return check;
}

- (NSString *)convertHtml:(NSString *)text
{
	NSString *exp = @"^(.*@)<a .*?>(.*?)</a>(.*)$";
	while ([text lengthOfRegularExpression:exp
							   withOptions:OgreMultilineOption])
	{
		text = [text replaceWithExpression:exp
								   replace:@"\\1\\2\\3"
								   options:OgreMultilineOption];
	}
	exp = @"^(.*)<a .*?>(\\#.*?)</a>(.*)$";
	while ([text lengthOfRegularExpression:exp
							   withOptions:OgreMultilineOption])
	{
		text = [text replaceWithExpression:exp
								   replace:@"\\1\\2\\3"
								   options:OgreMultilineOption];
	}
	exp = @"^(.*)<a .*?href=\"(.*?)\".*?>.*?</a>(.*)$";
	while ([text lengthOfRegularExpression:exp
							   withOptions:OgreMultilineOption])
	{
		text = [text replaceWithExpression:exp
								   replace:@"\\1\\2\\3"
								   options:OgreMultilineOption];
	}
	return text;
}

- (NSDictionary *)parse:(NSDictionary *)obj
			   withUser:(NSString *)user_id
				  first:(BOOL)first
{
	//LOG(@"[%@ parse]", [self className]);
	if (![obj isKindOfClass:[NSDictionary class]])
	{
		return nil;
	}
	//LOG2(@"[%@ parse]\n%@", [self className], obj);
	NSDictionary *item = nil;
	@try
	{
		id item_id = [obj valueForKey:@"id"];
		//		id comments = [obj valueForKey:@"comments"];
		NSString *notify = _notify;
		NSDictionary *from = [obj valueForKey:@"from"];
		NSString *from_id = [from valueForKey:@"id"];
		NSString *from_name = [from valueForKey:@"name"];
		if ([from_id isEqualToString:from_name])
		{
			from_name = @"";
		}
		else
		{
			from_name = [NSString stringWithFormat:@" / %@", from_name];
		}
		NSString *user_profile = [NSString stringWithFormat:@"http://friendfeed-api.com/v2/picture/%@", from_id];
		NSString *date = [obj valueForKey:@"date"];
		date = [date replaceWithExpression:@"T" replace:@" "];
		date = [date replaceWithExpression:@"Z$" replace:@" +0000"];
		int hasKeyword = -1;
		NSString *category = KindTimeline;
		NSString *group = @"";
		id to = [obj valueForKey:@"to"];
		if (to && [to isKindOfClass:[NSArray class]])
		{
			NSEnumerator *enumerator = [to objectEnumerator];
			id to_obj;
			while ((to_obj = [enumerator nextObject]))
			{
				if ([[to_obj valueForKey:@"type"] isEqualToString:@"group"])
				{
					NSString *to_name = [to_obj valueForKey:@"name"];
					if (to_name)
					{
						if ([group isEqualToString:@""])
						{
							group = [group stringByAppendingString:@" "];
						}
						group = [group stringByAppendingFormat:@" %@", to_name];
					}
				}
			}
		}
		NSString *source = nil;
		id via = [obj valueForKey:@"via"];
		if (via)
		{
			NSString *via_url = [via valueForKey:@"url"];
			NSString *via_name = [via valueForKey:@"name"];
			if (via_name)
			{
				if (via_url)
				{
					source = [NSString stringWithFormat:@"<a href=\"%@\">%@</a>", via_url, via_name];
				}
				else
				{
					source = [NSString stringWithFormat:@"%@", via_name];
				}
			}
		}
		[_userProfile setObject:user_profile forKey:user_id];
		if (item_id)
		{
			NSString *comment = nil;
			NSString *inReplyUser = nil;
			if ([item_id lengthOfRegularExpression:@"/c/"])
			{
				NSDictionary *reply = [self fetch:[item_id replaceWithExpression:@"^(.*)/c/.*$" replace:@"\\1"]
									  withService:_service];
				if (reply)
				{
					NSString *comment_group = [reply valueForKey:KeyChannel];
					if (comment_group && ![comment_group isEqualToString:@""])
					{
						group = [NSString stringWithFormat:@"  %@", comment_group];
					}
					inReplyUser = [reply valueForKey:KeyUser];
					comment = [NSString stringWithFormat:@"(on %@: %@)",
							   [reply valueForKey:KeyUser], [reply valueForKey:KeyText]];
				}
			}
			NSString *text = [obj valueForKey:@"body"];
			text = [self convertHtml:text];
			id thumbnails = [obj valueForKey:@"thumbnails"];
			if (thumbnails)
			{
				NSEnumerator *enumerator = [thumbnails objectEnumerator];
				id thumbnail;
				while ((thumbnail = [enumerator nextObject]))
				{
					NSString *thumbnail_url = [thumbnail valueForKey:@"url"];
					if (thumbnail_url)
					{
						text = [text stringByAppendingFormat:@"\n[%@]", thumbnail_url];
					}
				}
			}
			if (![from_id isEqualToString:user_id])
			{
				hasKeyword = [self hasKeywords:
							  [NSArray arrayWithObjects:
							   text, from_id, from_name, nil]];
				if (hasKeyword == 0)
				{
					notify = GrowlAfficheurKeywordsNotify;
					first = NO;
				}
			}
			if (![from_id isEqualToString:user_id] &&
				([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@$", user_id] withOptions:OgreMultilineOption] > 0) ||
				([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@[^!-~]", user_id] withOptions:OgreMultilineOption] > 0))
			{
				notify = _notifyReply;
				category = KindReply;
				first = NO;
			}
			item = [self createItem:item_id
						   withUser:from_id
							   name:from_name
							channel:group
								rid:nil
							   text:text
							comment:comment
							 source:source
						 created_at:[NSDate dateWithString:date]
					   user_profile:user_profile
							 notify:notify
							   kind:KindTimeline
						   category:category
						inReplyUser:inReplyUser
							keyword:hasKeyword
							  first:first];
		}
		//LOG(@"[%@(%@) parse]\n%@", [self className], _service, item);
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) parse] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return item;
}

- (NSString *)determinePost:(id)object
					withAPI:(FriendFeedAPI *)api
				   function:(NSString *)function
{
	id description = [NSString stringWithFormat:@"%@ was failure.", function];
	@try
	{
		LOG2(@"[%@(%@) determinPost] %@", [self className], _service, object);
		if ([object isKindOfClass:[NSError class]])
		{
			NSString *desc = [api errorLocalizedDescription:object];
			if (desc)
			{
				description = [NSString stringWithFormat:@"%@\n(%@)", description, desc];
			}
		}
		else if (![object isKindOfClass:[NSDictionary class]])
		{
			NSString *strring = [NSString stringWithFormat:@"%@", object];
			NSString *title = [api titleOfHTML:strring];
			if (title)
			{
				strring = title;
			}
			description = [NSString stringWithFormat:@"%@\n(%@)", description, strring];
		}
		else
		{
			NSString *error = [object valueForKey:@"errorCode"];
			if (error)
			{
				description = [description stringByAppendingFormat:@"\n(%@)", [object valueForKey:@"http_status_code"]];
			}
			else
			{
				description = nil;
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) determinPost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return description;
}

- (NSString *)determinePost:(id)object
					withAPI:(FriendFeedAPI *)api
{
	return [self determinePost:object
					   withAPI:api
					  function:@"Post"];
}

- (BOOL)addResult:(id)object
		  withAPI:(FriendFeedAPI *)api
{
	BOOL result = NO;
	NSMutableArray *list = [[NSMutableArray alloc] init];
	NSMutableArray *notify = [[NSMutableArray alloc] init];
	[self lockService];
	@try
	{
		id item = [self parse:object withUser:[api user] first:NO];
		LOG2(@"[%@(%@) determinPost] %@", [self className], _service, item);
		if (item)
		{
			[item retain];
			@try
			{
				[list addObject:item];
				if ([self addTimeline:item withFirst:NO])
				{
					//LOG(@"[%@(%@) performRetrieve]\n%@", [self className], _service, item);
					NSString *channel = [item valueForKey:KeyChannel];
					if (channel && ![channel isEqualToString:@""])
					{
						[self registComplete:channel];
					}
					if ([_preferences generalGrowlOldOrder])
					{
						[notify addObject:item];
					}
					else
					{
						[notify insertObject:item atIndex:0];
					}
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			@finally
			{
				[item release];
			}
			result = YES;
		}
		id comments = [object valueForKey:@"comments"];
		if (comments && [comments isKindOfClass:[NSArray class]])
		{
			NSEnumerator *enumerator = [comments objectEnumerator];
			id comment;
			while ((comment = [enumerator nextObject]))
			{
				item = [self parse:comment withUser:[api user] first:NO];
				if (item)
				{
					[item retain];
					@try
					{
						[list addObject:item];
						if ([self addTimeline:item withFirst:NO])
						{
							//LOG(@"[%@(%@) performRetrieve]\n%@", [self className], _service, item);
							NSString *channel = [item valueForKey:KeyChannel];
							if (channel && ![channel isEqualToString:@""])
							{
								[self registComplete:channel];
							}
							if ([_preferences generalGrowlOldOrder])
							{
								[notify addObject:item];
							}
							else
							{
								[notify insertObject:item atIndex:0];
							}
						}
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
					}
					@finally
					{
						[item release];
					}
				}
			}
		}
		[self finishAddTimeline:list withNotify:notify];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[self unlockService];
		[notify release];
		[list release];
	}
	return result;
}

- (NSString *)determinePost:(id)object
					withAPI:(FriendFeedAPI *)api
					 status:(NSString *)status
{
	LOG(@"[%@(%@) determinPost] %@", [self className], _service, object);
	id description = @"Post was failure.";
	@try
	{
		//LOG(@"[%@(%@) determinPost] %@", [self className], _service, [api className]);
		if ([object isKindOfClass:[NSError class]])
		{
			NSString *desc = [api errorLocalizedDescription:object];
			if (desc)
			{
				description = [NSString stringWithFormat:@"%@\n(%@)", description, desc];
			}
		}
		else if (![object isKindOfClass:[NSDictionary class]])
		{
			NSString *strring = [NSString stringWithFormat:@"%@", object];
			NSString *title = [api titleOfHTML:strring];
			if (title)
			{
				strring = title;
			}
			description = [NSString stringWithFormat:@"%@\n(%@)", description, strring];
		}
		else
		{
			LOG2(@"[%@(%@) determinPost]\n%@", [self className], _service, object);
			if ([self addResult:object withAPI:api])
			{
				description = nil;
			}
#if 0
			NSString *body = [object valueForKey:@"body"];
			if (body)
			{
				NSString *body2 = [self convertText:body];
				NSString *status2 = [self convertText:status];
				body2 = [self convertHtml:body2];
				LOG2(@"[%@(%@) determinPost]\n\'%@\'\n\'%@\'", [self className], _service, body2, status2);
				if ([body2 isEqualTo:status2])
				{
					description = nil;
				}
			}
#endif
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) determinPost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return description;
}

- (NSString *)fairingMessage:(NSString *)message
{
	while ([message lengthOfRegularExpression:@"\\s$"])
	{
		message = [message replaceWithExpression:@"\\s$"
										 replace:@""
										 options:OgreMultilineOption];
	}
	message = [message replaceWithExpression:@"\\r\\n" replace:@"\n"];
	message = [message replaceWithExpression:@"\\r" replace:@"\n"];
	message = [message replaceWithExpression:@"\\n" replace:@"\r\n"];
	if ([_preferences generalConvertEmoji])
	{
		message = [self stringFromEmoji:message];
	}
	return message;
}

- (void)performPost:(NSArray *)array
{
	@try
	{
		[_controller syncPost];
		FriendFeedAPI* api = [self createAPI:[array objectAtIndex:1]];
		if (api)
		{
			NSString *status = [array objectAtIndex:0];
			NSString *entry_id = nil;
			status = [self fairingMessage:status];
			[self registComplete:status];
			id result = nil;
			if ([array count] > 2)
			{
				entry_id = [array objectAtIndex:2];
			}
			if (entry_id && ![entry_id isEqualToString:@""])
			{
				result = [api comment:status withID:entry_id];
			}
			else
			{
				if ([status lengthOfRegularExpression:@"^\\!"])
				{
					NSString *exp = @"^\\!(\\S+)\\s+(.*)$";
					NSString *group = [status replaceWithExpression:exp
															replace:@"\\1"
															options:OgreMultilineOption];
					status = [status replaceWithExpression:exp
												   replace:@"\\2"
												   options:OgreMultilineOption];
					result = [api entry:status to:group];
				}
				else
				{
					result = [api entry:status];
				}
			}
			[self determinePost:[self determinePost:result withAPI:api status:status]
					  withTitle:_title
					   function:@"Post"];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performPost] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[array release];
	}
}

- (void)post:(NSString *)status
{
	[self post:status withReply:nil];
}

- (void)post:(NSString *)status
   withReply:(NSString *)item_id
{
	NSString *user = [_preferences accountFriendFeedUser];
	NSString *key = [_preferences accountFriendFeedOAuthKey];
	NSString *secret = [_preferences accountFriendFeedOAuthSecret];
	NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
						 user, FriendFeedUser,
						 key, FriendFeedKey,
						 secret, FriendFeedSecret,
						 nil];
	[self performPost:[[NSArray alloc] initWithObjects:status, dic, item_id, nil]];
}

- (BOOL)isReply:(NSDictionary *)object
{
	return [[object valueForKey:KeyNotify] isEqualToString:_notifyReply];
}

- (BOOL)isChannel:(NSDictionary *)object
{
	return [[object valueForKey:KeyNotify] isEqualToString:GrowlAfficheurFriendFeedGroupNotify];
}

- (void)performRetrieve:(FriendFeedAPI *)api
			 withObject:(id)object
				   kind:(int)kind
				  first:(BOOL)first
{
	//LOG(@"[%@(%@) performRetrieve:%d] %@", [self className], _service, kind, [object className]);
	@try
	{
		id description = nil;
		switch (kind)
		{
			default:
			case kindTimeline:
				description = @"Retrieve timeline was failure.";
				break;
			case kindReply:
				description = @"Retrieve replies was failure.";
				break;
			case kindDM:
				description = @"Retrieve direct message was failure.";
				break;
			case kindChannel:
				description = @"Retrieve channel was failure.";
				break;
		}
		if ([object isKindOfClass:[NSError class]])
		{
			NSString *desc = [api errorLocalizedDescription:object];
			if (desc)
			{
				description = [NSString stringWithFormat:@"%@\n(%@)", description, desc];
			}
		}
		if (![object isKindOfClass:[NSDictionary class]])
		{
			if ([object isKindOfClass:[NSString class]])
			{
				if ([HTTP isHTML:object])
				{
					NSString *title = [api titleOfHTML:object];
					if (title)
					{
						description = [NSString stringWithFormat:@"%@\n(%@)", description, title];
					}
				}
				else
				{
#if defined(_D_E_B_U_G_)
					description = [NSString stringWithFormat:@"%@\n(%@)", description, object];
#else //defined(_D_E_B_U_G_)
					description = [NSString stringWithFormat:@"%@", description];
#endif //defined(_D_E_B_U_G_)
				}
			}
			NSDictionary *item = [[self createItem:IdZero
										  withUser:@""
											  name:@""
										   channel:@""
											   rid:@""
											  text:[NSString stringWithFormat:@"%@", description]
										   comment:@""
											source:nil
										created_at:[NSDate dateWithTimeIntervalSinceNow:0.0]
									  user_profile:_errorIcon
											notify:_notifyError
											  kind:KindError
										  category:KindError
									   inReplyUser:nil
											 first:NO] retain];
			@try
			{
				[self addTimeline:item withFirst:NO];
				NSArray *notify = [NSArray arrayWithObjects:item, nil];
				[self finishAddTimeline:notify withNotify:notify];
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			@finally
			{
				[item release];
			}
		}
		else
		{
			id entries = [object valueForKey:@"entries"];
			if (entries && [entries isKindOfClass:[NSArray class]])
			{
				NSMutableArray *list = [[NSMutableArray alloc] init];
				NSMutableArray *notify = [[NSMutableArray alloc] init];
				@try
				{
					int i = [entries count];
					while (i-- > 0)
					{
						id obj = [entries objectAtIndex:i];
						id item = [self parse:obj withUser:[api user] first:first];
						if (item)
						{
							[item retain];
							@try
							{
								[list addObject:item];
								if ([self addTimeline:item withFirst:first])
								{
									//LOG(@"[%@(%@) performRetrieve]\n%@", [self className], _service, item);
									NSString *channel = [item valueForKey:KeyChannel];
									if (channel && ![channel isEqualToString:@""])
									{
										[self registComplete:channel];
									}
									if ([_preferences generalGrowlOldOrder])
									{
										[notify addObject:item];
									}
									else
									{
										[notify insertObject:item atIndex:0];
									}
								}
							}
							@catch (NSException *exception)
							{
								EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
							}
							@finally
							{
								[item release];
							}
						}
						id comments = [obj valueForKey:@"comments"];
						if (comments && [comments isKindOfClass:[NSArray class]])
						{
							NSEnumerator *enumerator = [comments objectEnumerator];
							id comment;
							while ((comment = [enumerator nextObject]))
							{
								item = [self parse:comment withUser:[api user] first:first];
								if (item)
								{
									[item retain];
									@try
									{
										[list addObject:item];
										if ([self addTimeline:item withFirst:first])
										{
											//LOG(@"[%@(%@) performRetrieve]\n%@", [self className], _service, item);
											NSString *channel = [item valueForKey:KeyChannel];
											if (channel && ![channel isEqualToString:@""])
											{
												[self registComplete:channel];
											}
											if ([_preferences generalGrowlOldOrder])
											{
												[notify addObject:item];
											}
											else
											{
												[notify insertObject:item atIndex:0];
											}
										}
									}
									@catch (NSException *exception)
									{
										EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
									}
									@finally
									{
										[item release];
									}
								}
							}
						}
					}
					[self finishAddTimeline:list withNotify:notify];
				}
				@catch (NSException *exception)
				{
					EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
				}
				@finally
				{
					[notify release];
					[list release];
				}
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performRetrieve] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

- (void)performRetrieveHomeFeed:(NSDictionary *)dic
{
	//LOG(@"[%@(%@) performRetrieveHomeFeed]", [self className], _service);
	[self setupRetrieveDateTimeline:nil];
	@try
	{
		
		FriendFeedAPI *api = [self createAPI:dic];
		if (api)
		{
			[api setTimeoutInterval:30];
			BOOL success = NO;
			id result;
			int retry = 2;
			while (retry--)
			{
				result = [api homeFeed];
				if (result && ![result isKindOfClass:[NSError class]])
				{
					success = YES;
					break;
				}
			}
			LOG(@"[%@(%@) performRetrieveHomeFeed] lock", [self className], _service);
			[self lockService];
			LOG(@"[%@(%@) performRetrieveHomeFeed] locked", [self className], _service);
			@try
			{
				[self performRetrieve:api withObject:result kind:kindTimeline first:_first];
				if (success && _first)
				{
					_first = NO;
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) performRetrieveHomeFeed] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			@finally
			{
				LOG(@"[%@(%@) performRetrieveHomeFeed] unlock", [self className], _service);
				[self unlockService];
				LOG(@"[%@(%@) performRetrieveHomeFeed] unlocked", [self className], _service);
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performRetrieveHomeFeed] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[dic release];
		[self setupRetrieveDateTimeline:[NSDate dateWithTimeIntervalSinceNow:0]];
	}
}

- (void)performFavorite:(NSArray *)object
{
	//LOG(@"[%@(%@) performFavorite]", [self className], _service);
	@try
	{
		NSString *item_id = [object objectAtIndex:0];
		NSDictionary *dic = [object objectAtIndex:1];
		FriendFeedAPI *api = [self createAPI:dic];		
		if (api)
		{
			[api setTimeoutInterval:30];
			id result = [api like:item_id];
			[self determinePost:[self determinePost:result withAPI:api function:@"Like"]
					  withTitle:_title
					   function:@"Like"];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performFavorite] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	@finally
	{
		[object release];
	}
}

- (void)retrieveHomeFeed
{
	//LOG(@"[%@(%@) retrieveContactsFeed]", [self className], _service);
	BOOL retrieve = NO;
	@synchronized(self)
	{
		if (_dateTimeline)
		{
			retrieve = YES;
			[self setupRetrieveDateTimeline:nil];
		}
	}
	if (retrieve)
	{
		@try
		{
			NSString *user = [_preferences accountFriendFeedUser];
			NSString *key = [_preferences accountFriendFeedOAuthKey];
			NSString *secret = [_preferences accountFriendFeedOAuthSecret];
			NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
								 user, FriendFeedUser,
								 key, FriendFeedKey,
								 secret, FriendFeedSecret,
								 nil];
			[self performRetrieveHomeFeed:dic];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) retrieveContactsFeed] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (void)doReply:(NSString *)item_id
	   withText:(BOOL)withText
{
	@try
	{
		LOG2(@"[%@(%@) doReply]\n%@", [self className], _service, item_id);
		id obj = [_controller fetchTimelineALL:item_id withService:FriendFeed];
		if (obj)
		{
			NSString *replyTo = @"";
			item_id = [[item_id componentsSeparatedByString:@"/c/"] objectAtIndex:0];
			replyTo = [NSString stringWithFormat:@"Comment to: %@%@",
					   [obj valueForKey:KeyText], [obj valueForKey:KeyComment]];
			LOG2(@"[%@(%@) doReply]\nreply to: %@\n%@", [self className], _service, [obj valueForKey:KeyInReplyUser], item_id);
			NSString *text = @"";
			if (withText)
			{
				text = [NSString stringWithFormat:@"@%@ ", [obj valueForKey:KeyUser]];
			}
			[_controller setReply:[self replyAttributedString:replyTo]
					  withService:FriendFeed
						inReplyTo:item_id
							 text:text];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) doReply] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

- (void)favorite:(NSString *)item_id
{
	//LOG(@"[%@(%@) favorite]", [self className], _service);
	@synchronized(self)
	{
		@try
		{
			id item = [self fetch:item_id withService:_service];
			if (item)
			{
				LOG2(@"[%@(%@) favorite]\n%@", [self className], _service, item_id);
				NSString *user = [_preferences accountFriendFeedUser];
				NSString *key = [_preferences accountFriendFeedOAuthKey];
				NSString *secret = [_preferences accountFriendFeedOAuthSecret];
				if (!key)
				{
					key = (NSString *)[NSNull null];
				}
				if (!secret)
				{
					secret = (NSString *)[NSNull null];
				}
				NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
									 user, FriendFeedUser,
									 key, FriendFeedKey,
									 secret, FriendFeedSecret,
									 nil];
				item_id = [[item_id componentsSeparatedByString:@"/c/"] objectAtIndex:0];
				[self performFavorite:[[NSArray alloc] initWithObjects:item_id, dic, nil]];
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@(%@) favorite] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
		}
	}
}

- (NSString *)permalinkURL:(NSString *)item_id
{
	NSString *url = nil;
	@try
	{
		LOG2(@"[%@(%@) permalinkURL]\n%@", [self className], _service, item_id);
		id item = [self fetch:item_id withService:_service];
		if (item)
		{
			url = [NSString stringWithFormat:@"http://friendfeed.com/%@",[item valueForKey:KeyUser]];
			LOG2(@"[%@(%@) permalinkURL]\n%@", [self className], _service, url);
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) permalinkURL] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
	return url;
}

- (void)permalink:(NSString *)item_id
{
	@try
	{
		LOG2(@"[%@(%@) permalink]\n%@", [self className], _service, item_id);
		NSString *url = [self permalinkURL:item_id];
		if (url)
		{
			LOG2(@"[%@(%@) permalink]\n%@", [self className], _service, url);
			[self doOpenURL:url];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) permalink] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

- (void)performReceiveMessage:(NSXMLElement *)message
{
	@try
	{
		//LOG(@"[%@(%@) performReceiveMessage]", [self className], _service);
		NSDate *created_at = [NSDate date];
		BOOL valid = YES;
		NSString *stamp = nil;
		NSXMLElement *x = [message elementForName:@"x" xmlns:@"jabber:x:delay"];
		if (x)
		{
			//LOG(@"[%@(%@) performReceiveMessage]\nx = %@", [self className], _service, x);
			stamp = [[x attributeForName:@"stamp"] stringValue];
			if (stamp)
			{
				//LOG(@"[%@(%@) performReceiveMessage]\nstamp = %@", [self className], _service, stamp);
				if (![_preferences accountIdenticaOfflineXMPP])
				{
					valid = NO;
				}
				else if ([stamp lengthOfRegularExpression:@"\\d\\d\\d\\d\\d\\d\\d\\dT\\d\\d\\:\\d\\d\\:\\d\\d"] > 0)
				{
					//20090718T21:21:06
					stamp = [stamp replaceWithExpression:@"(....)(..)(..)T(..\\:..\\:..)"
												 replace:@"\\1-\\2-\\3 \\4 +0000"];
					created_at = [NSDate dateWithString:stamp];
					LOG(@"[%@(%@) performReceiveMessage]\ncreated_at = %@", [self className], _service, created_at);
				}
				else
				{
					valid = NO;
				}
			}
		}
		if (valid)
		{
			BOOL doRetrieve = NO;
			[self lockService];
			@try
			{
				LOG(@"[%@(%@) performReceiveMessage]\n%@", [self className], _service, message);
				NSString *body = [[message elementForName:@"body"] stringValue];
				LOG(@"[%@(%@) performReceiveMessage]\nbody = %@", [self className], _service, body);
				NSString *user = nil;
				NSString *reply = nil;
				NSString *channel = @"";
				NSString *text = nil;
				NSString *item_id = nil;
				NSString *comment = nil;
				NSString *parent_id = nil;
				NSString *date = [created_at descriptionWithCalendarFormat:@"%Y%m%d%H%M%S%F" timeZone:nil locale:nil];
				if ([body rangeOfRegularExpressionString:@"^\\S+#\\S+: .*\nLink"].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] #", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+)#(\\S+):.*$" replace:@"\\1" options:OgreMultilineOption];
					channel = [body replaceWithExpression:@"^(\\S+)#(\\S+):.*$" replace:@" #\\2" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^\\S+#\\S+: (.*)\nLink: (.*)$" replace:@"\\1" options:OgreMultilineOption];
					item_id = [body replaceWithExpression:@"^\\S+#\\S+: (.*)\nLink: (.*)$" replace:@"\\2" options:OgreMultilineOption];
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+ \\(to \\S+ on #\\S+\\): "].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] #comment", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+) \\(to (\\S+) on #(\\S+)\\):.*$" replace:@"\\1" options:OgreMultilineOption];
					reply = [body replaceWithExpression:@"^(\\S+) \\(to (\\S+) on #(\\S+)\\):.*$" replace:@"\\2" options:OgreMultilineOption];
					channel = [body replaceWithExpression:@"^(\\S+) \\(to (\\S+) on #(\\S+)\\):.*$" replace:@" #\\3" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^\\S+ \\(to \\S+ on #\\S+\\): (.*)\nLink: (.*)$" replace:@"\\1" options:OgreMultilineOption];
					item_id = [body replaceWithExpression:@"^\\S+ \\(to \\S+ on #\\S+\\): (.*)\nLink: (.*)$" replace:@"\\2" options:OgreMultilineOption];
					parent_id = [NSString stringWithString:item_id];
					item_id = [item_id stringByAppendingFormat:@"-%@", date];
					comment = [text replaceWithExpression:@"^(.*) \\(on (.*)\\)" replace:@"\\2" options:OgreMultilineOption];
					text = [text replaceWithExpression:@"^(.*) \\(on (.*)\\)" replace:@"\\1" options:OgreMultilineOption];
					comment = [NSString stringWithFormat:@"(on%@: %@)", channel, comment];
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+#\\S+: .*\\(.*\\)$" options:OgreMultilineOption].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] #comment", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+)#(\\S+):.*$" replace:@"\\1" options:OgreMultilineOption];
					channel = [body replaceWithExpression:@"^(\\S+)#(\\S+):.*$" replace:@" #\\2" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^\\S+#\\S+: (.*)\\(.*\\)$" replace:@"\\1" options:OgreMultilineOption];
					comment = [body replaceWithExpression:@"^\\S+#\\S+: (.*)\\(on (.*)\\)$" replace:@"\\2" options:OgreMultilineOption];
					//item_id = [body replaceWithExpression:@"^\\S+#\\S+: (.*)\nLink: (.*)$" replace:@"\\2" options:OgreMultilineOption];
					item_id = date;
					comment = [NSString stringWithFormat:@"(on%@: %@)", channel, comment];
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+#\\S+: "].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] #", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+)#(\\S+):.*$" replace:@"\\1" options:OgreMultilineOption];
					channel = [body replaceWithExpression:@"^(\\S+)#(\\S+):.*$" replace:@" #\\2" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^\\S+#\\S+: (.*)$" replace:@"\\1" options:OgreMultilineOption];
					//item_id = [body replaceWithExpression:@"^\\S+#\\S+: (.*)\nLink: (.*)$" replace:@"\\2" options:OgreMultilineOption];
					item_id = date;
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+ \\(to \\S+\\): "].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] comment", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+) \\(to (\\S+)\\):.*$" replace:@"\\1" options:OgreMultilineOption];
					reply = [body replaceWithExpression:@"^(\\S+) \\(to (\\S+)\\):.*$" replace:@"\\2" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^\\S+ \\(to \\S+\\): (.*)\nLink: (.*)" replace:@"\\1" options:OgreMultilineOption];
					item_id = [body replaceWithExpression:@"^\\S+ \\(to \\S+\\): (.*)\nLink: (.*)" replace:@"\\2" options:OgreMultilineOption];
					parent_id = [NSString stringWithString:item_id];
					item_id = [item_id stringByAppendingFormat:@"-%@", date];
					comment = [text replaceWithExpression:@"^(.*) \\(on (.*)\\)" replace:@"\\2" options:OgreMultilineOption];
					text = [text replaceWithExpression:@"^(.*) \\(on (.*)\\)" replace:@"\\1" options:OgreMultilineOption];
					comment = [NSString stringWithFormat:@"(on %@: %@)", reply, comment];
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+: .* \\(on .*\\)$" options:OgreMultilineOption].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] comment", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+): (.*) \\(on (.*)\\)$" replace:@"\\1" options:OgreMultilineOption];
					//reply = [body replaceWithExpression:@"^(\\S+): (.*) \\(on (.*)\\)$" replace:@"\\2" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^(\\S+): (.*) \\(on (.*)\\)$" replace:@"\\2" options:OgreMultilineOption];
					//item_id = [body replaceWithExpression:@"^(\\S+): (.*) \\(on (.*)\\)" replace:@"\\2" options:OgreMultilineOption];
					//parent_id = [NSString stringWithString:item_id];
					//item_id = [item_id stringByAppendingFormat:@"-%@", date];
					comment = [body replaceWithExpression:@"^(\\S+): (.*) \\(on (.*)\\)$" replace:@"\\3" options:OgreMultilineOption];
					comment = [NSString stringWithFormat:@"(on %@)", comment];
					item_id = date;
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+: .*\nLink"].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] text", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+):.*$" replace:@"\\1" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^\\S+: (.*)\nLink: (.*)" replace:@"\\1" options:OgreMultilineOption];
					item_id = [body replaceWithExpression:@"^\\S+: (.*)\nLink: (.*)" replace:@"\\2" options:OgreMultilineOption];
				}
				else if ([body rangeOfRegularExpressionString:@"^\\S+: "].length > 0)
				{
					LOG(@"[%@(%@) performReceiveMessage] text", [self className], _service);
					user = [body replaceWithExpression:@"^(\\S+): (.*)$" replace:@"\\1" options:OgreMultilineOption];
					text = [body replaceWithExpression:@"^(\\S+): (.*)$" replace:@"\\2" options:OgreMultilineOption];
					//item_id = [body replaceWithExpression:@"^\\S+: (.*)\nLink: (.*)" replace:@"\\2" options:OgreMultilineOption];
					item_id = date;
				}
				text = [text replaceWithExpression:@"\\r\\n" replace:@"\n"];
				text = [text replaceWithExpression:@"\\r" replace:@"\n"];
				if (comment)
				{
					comment = [comment replaceWithExpression:@"\\r" replace:@""];
				}
				NSString *name = nil;
				NSString *icon = nil;
				NSString *xmpp_id = nil;
				NSString *published = nil;
				NSXMLElement *entry = [message elementForName:@"entry"];
				if (entry)
				{
					NSXMLElement *source = [entry elementForName:@"source"];
					if (source)
					{
						NSXMLElement *author = [source elementForName:@"author"];
						if (author)
						{
							name = [[author elementForName:@"name"] stringValue];
						}
						icon = [[source elementForName:@"icon"] stringValue];
					}
					xmpp_id = [[entry elementForName:@"id"] stringValue];
					published = [[entry elementForName:@"published"] stringValue];
					if (name)
					{
						user = name;
					}
					if (icon)
					{
						icon = [icon replaceWithExpression:@"^(.*)\\%40(.*)\\_None(.*)$"
												   replace:@"\\1@\\2_t\\3"];
					}
					if (xmpp_id)
					{
						if ([xmpp_id lengthOfRegularExpression:@".*\\#c\\-.*"])
						{
							item_id = [xmpp_id replaceWithExpression:@"^(.*)\\#c(.*)$"
															 replace:@"\\1\\2"];
						}
						else
						{
							item_id = xmpp_id;
						}
					}
					if (published)
					{
						published = [published replaceWithExpression:@"^(.*)T(.*)Z$"
															 replace:@"\\1 \\2 +0000"];
						created_at = [NSDate dateWithString:published];
					}
				}
				LOG(@"[%@(%@) performReceiveMessage]"\
					@"\nuser    = %@"\
					@"\nicon    = %@"\
					@"\nchannel = %@"\
					@"\ntext    = %@"\
					@"\nitem_id  = %@"\
					@"\nreply   = %@"\
					@"\ncomment = %@"\
					@"\ncreated_at = %@",
					[self className], _service, user, icon, channel, text, item_id, reply, comment, created_at);
				if (user && text && item_id)
				{
					NSString *category = KindTimeline;
					NSString *notify = _notify;
					NSString *user_id = [_preferences accountFriendFeedUser];
					if (![channel isEqualToString:@""])
					{
						notify = GrowlAfficheurFriendFeedGroupNotify;
					}
					int hasKeyword = -1;
					if (![user isEqualToString:user_id])
					{
						hasKeyword = [self hasKeywords:
									  [NSArray arrayWithObjects:
									   text, user, channel, nil]];
						if (hasKeyword == 0)
						{
							notify = GrowlAfficheurKeywordsNotify;
						}
					}
					if (![user_id isEqualToString:user] && reply && [reply isEqualToString:user_id])
					{
						notify = _notifyReply;
						category = KindReply;
					}
					if (![user_id isEqualToString:user] && parent_id && [self begins:parent_id withService:FriendFeed user:user_id])
					{
						notify = _notifyReply;
						category = KindReply;
					}
					if (([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@$", user_id]] > 0) ||
						([text lengthOfRegularExpression:[NSString stringWithFormat:@"@%@[^!-~]", user_id]] > 0))
					{
						notify = _notifyReply;
						category = KindReply;
					}
					if (!icon && ![_userProfile valueForKey:user])
					{
						NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
											 [_preferences accountFriendFeedUser], FriendFeedUser,
											 [_preferences accountFriendFeedOAuthKey], FriendFeedKey,
											 [_preferences accountFriendFeedOAuthSecret], FriendFeedSecret,
											 nil];
						FriendFeedAPI *api = [self createAPI:dic];
						if (api)
						{
							[api setTimeoutInterval:10];
#if 0
							id result = [api userInfo:user];
							if ([result isKindOfClass:[NSDictionary class]])
							{
								//LOG(@"[%@(%@) performReceiveMessage]\nuserInfo = %@", [self className], _service, result);
								NSString *avatar_user = [result valueForKey:@"user"];
								if (avatar_user)
								{
									NSString *avatar = [avatar_user valueForKey:@"avatar"];
									if (avatar)
									{
										avatar = [avatar replaceWithExpression:@"%40" replace:@"@"];
										[_userProfile setObject:avatar forKey:user];
									}
								}
							}
#endif
						}
						icon = [_userProfile valueForKey:user];
					}
					NSDictionary *item = [self createItem:item_id
												 withUser:user
													 name:@""
												  channel:channel
													  rid:@""
													 text:text
												  comment:comment
												   source:IM
											   created_at:created_at
											 user_profile:icon
												   notify:notify
													 kind:KindTimeline
												 category:category
											  inReplyUser:reply
												  keyword:hasKeyword
													first:NO
													 xmpp:YES];
					if (item)
					{
						[item retain];
						if (![item valueForKey:KeyFrom])
						{
							LOG(@"[%@(%@) performReceiveMessage]\nitem = %@", [self className], _service, item);
						}
						NSMutableArray *list = [[NSMutableArray alloc] init];
						NSMutableArray *notify = [[NSMutableArray alloc] init];
						@try
						{
							if ([self addTimeline:item withFirst:NO])
							{
								//LOG(@"[%@(%@) performReceiveMessage]\n%@", [self className], _service, item);
								//LOG(@"[%@(%@) performReceiveMessage]\nnotify = %@", [self className], _service, [item valueForKey:KeyId]);
								if (![channel isEqualToString:@""])
								{
									[self registComplete:channel];
								}
								[notify insertObject:item atIndex:0];
								if (!stamp)
								{
									_xmppCount++;
								}
							}
							[list addObject:item];
							[self finishAddTimeline:list withNotify:notify];
							int retrieveCount = [_preferences accountIdenticaRetrieveXMPP];
							//LOG(@"[%@(%@) performReceiveMessage]\nretrieve = %d", [self className], _service, retrieve);
							if (retrieveCount > 0)
							{
								if (_xmppCount >= retrieveCount)
								{
									LOG(@"[%@(%@) performReceiveMessage#lock] retrieve", [self className], _service);
									_xmppCount = 0;
									doRetrieve = YES;
									//[self retrieveTimeline];
								}
							}
							else
							{
								_xmppCount = 0;
							}
						}
						@catch (NSException *exception)
						{
							EXPLOG(@"[%@(%@) performReceiveMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
						}
						@finally
						{
							[notify release];
							[list release];
							[item release];
						}
					}
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@(%@) performReceiveMessage#lock] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
			}
			@finally
			{
				[self unlockService];
			}
			if (doRetrieve)
			{
				LOG(@"[%@(%@) performReceiveMessage#lock] doRetrieve", [self className], _service);
				[self retrieveHomeFeed];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@(%@) performReceiveMessage] EXCEPTION\n%@: %@", [self className], _service, [exception name], [exception reason]);
	}
}

@end
