You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdenetwork/kopete/protocols/oscar/liboscar/client.cpp

1353 lines
39 KiB

/*
client.cpp - Kopete Oscar Protocol
Copyright (c) 2004-2005 Matt Rogers <mattr@kde.org>
Based on code Copyright (c) 2004 SuSE Linux AG <http://www.suse.com>
Based on Iris, Copyright (C) 2003 Justin Karneges
Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org>
*************************************************************************
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
*************************************************************************
*/
#include "client.h"
#include <tqtimer.h>
#include <tqtextcodec.h>
#include <kdebug.h> //for kdDebug()
#include <tdelocale.h>
#include "buddyicontask.h"
#include "clientreadytask.h"
#include "connectionhandler.h"
#include "changevisibilitytask.h"
#include "chatnavservicetask.h"
#include "errortask.h"
#include "icquserinfo.h"
#include "icquserinfotask.h"
#include "logintask.h"
#include "connection.h"
#include "messagereceivertask.h"
#include "onlinenotifiertask.h"
#include "oscarclientstream.h"
#include "oscarconnector.h"
#include "oscarsettings.h"
#include "oscarutils.h"
#include "ownuserinfotask.h"
#include "profiletask.h"
#include "senddcinfotask.h"
#include "sendmessagetask.h"
#include "serverredirecttask.h"
#include "servicesetuptask.h"
#include "ssimanager.h"
#include "ssimodifytask.h"
#include "ssiauthtask.h"
#include "offlinemessagestask.h"
#include "task.h"
#include "typingnotifytask.h"
#include "userinfotask.h"
#include "usersearchtask.h"
#include "warningtask.h"
#include "chatservicetask.h"
#include "rateclassmanager.h"
namespace
{
class DefaultCodecProvider : public Client::CodecProvider
{
public:
virtual TQTextCodec* codecForContact( const TQString& ) const
{
return TQTextCodec::codecForMib( 4 );
}
virtual TQTextCodec* codecForAccount() const
{
return TQTextCodec::codecForMib( 4 );
}
};
DefaultCodecProvider defaultCodecProvider;
}
class Client::ClientPrivate
{
public:
ClientPrivate() {}
TQString host, user, pass;
uint port;
int tzoffset;
bool active;
enum { StageOne, StageTwo };
int stage;
//Protocol specific data
bool isIcq;
bool redirectRequested;
TQValueList<WORD> redirectionServices;
WORD currentRedirect;
TQByteArray cookie;
DWORD connectAsStatus; // icq only
TQString connectWithMessage; // icq only
Oscar::Settings* settings;
//Tasks
ErrorTask* errorTask;
OnlineNotifierTask* onlineNotifier;
OwnUserInfoTask* ownStatusTask;
MessageReceiverTask* messageReceiverTask;
SSIAuthTask* ssiAuthTask;
ICQUserInfoRequestTask* icqInfoTask;
UserInfoTask* userInfoTask;
TypingNotifyTask * typingNotifyTask;
SSIModifyTask* ssiModifyTask;
//Managers
SSIManager* ssiManager;
ConnectionHandler connections;
//Our Userinfo
UserDetails ourDetails;
//Infos
TQValueList<int> exchanges;
TQString statusMessage; // for away-,DND-message etc...
//away messages
struct AwayMsgRequest
{
TQString contact;
ICQStatus contactStatus;
};
TQValueList<AwayMsgRequest> awayMsgRequestQueue;
TQTimer* awayMsgRequestTimer;
CodecProvider* codecProvider;
const Oscar::ClientVersion* version;
};
Client::Client( TQObject* parent )
:TQObject( parent, "oscarclient" )
{
m_loginTask = 0L;
m_loginTaskTwo = 0L;
d = new ClientPrivate;
d->tzoffset = 0;
d->active = false;
d->isIcq = false; //default to AIM
d->redirectRequested = false;
d->currentRedirect = 0;
d->connectAsStatus = 0x0; // default to online
d->ssiManager = new SSIManager( this );
d->settings = new Oscar::Settings();
d->errorTask = 0L;
d->onlineNotifier = 0L;
d->ownStatusTask = 0L;
d->messageReceiverTask = 0L;
d->ssiAuthTask = 0L;
d->icqInfoTask = 0L;
d->userInfoTask = 0L;
d->stage = ClientPrivate::StageOne;
d->typingNotifyTask = 0L;
d->ssiModifyTask = 0L;
d->awayMsgRequestTimer = new TQTimer();
d->codecProvider = &defaultCodecProvider;
connect( this, TQ_SIGNAL( redirectionFinished( WORD ) ),
this, TQ_SLOT( checkRedirectionQueue( WORD ) ) );
connect( d->awayMsgRequestTimer, TQ_SIGNAL( timeout() ),
this, TQ_SLOT( nextICQAwayMessageRequest() ) );
}
Client::~Client()
{
//delete the connections differently than in deleteConnections()
//deleteLater() seems to cause destruction order issues
deleteStaticTasks();
delete d->settings;
delete d->ssiManager;
delete d->awayMsgRequestTimer;
delete d;
}
Oscar::Settings* Client::clientSettings() const
{
return d->settings;
}
void Client::connectToServer( Connection *c, const TQString& server, bool auth )
{
d->connections.append( c );
if ( auth == true )
{
m_loginTask = new StageOneLoginTask( c->rootTask() );
connect( m_loginTask, TQ_SIGNAL( finished() ), this, TQ_SLOT( lt_loginFinished() ) );
}
connect( c, TQ_SIGNAL( socketError( int, const TQString& ) ), this, TQ_SLOT( determineDisconnection( int, const TQString& ) ) );
c->connectToServer(server, auth);
}
void Client::start( const TQString &host, const uint port, const TQString &userId, const TQString &pass )
{
Q_UNUSED( host );
Q_UNUSED( port );
d->user = userId;
d->pass = pass;
d->stage = ClientPrivate::StageOne;
d->active = false;
}
void Client::close()
{
d->active = false;
d->awayMsgRequestTimer->stop();
d->awayMsgRequestQueue.clear();
d->connections.clear();
deleteStaticTasks();
//don't clear the stored status between stage one and two
if ( d->stage == ClientPrivate::StageTwo )
{
d->connectAsStatus = 0x0;
d->connectWithMessage = TQString();
}
d->exchanges.clear();
d->redirectRequested = false;
d->currentRedirect = 0;
d->redirectionServices.clear();
d->ssiManager->clear();
}
void Client::setStatus( AIMStatus status, const TQString &_message )
{
// AIM: you're away exactly when your away message isn't empty.
// can't use TQString() as a message either; ProfileTask
// interprets null as "don't change".
TQString message;
if ( status == Online )
message = TQString::fromAscii("");
else
{
if ( _message.isEmpty() )
message = TQString::fromAscii(" ");
else
message = _message;
}
Connection* c = d->connections.connectionForFamily( 0x0002 );
if ( !c )
return;
ProfileTask* pt = new ProfileTask( c->rootTask() );
pt->setAwayMessage( message );
pt->go( true );
}
void Client::setStatus( DWORD status, const TQString &message )
{
// remember the message to reply with, when requested
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Setting status message to "<< message << endl;
d->statusMessage = message;
// ICQ: if we're active, set status. otherwise, just store the status for later.
if ( d->active )
{
//the first connection is always the BOS connection
Connection* c = d->connections.connectionForFamily( 0x0013 );
if ( !c )
return; //TODO trigger an error of some sort?
ChangeVisibilityTask* cvt = new ChangeVisibilityTask( c->rootTask() );
if ( ( status & 0x0100 ) == 0x0100 )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Setting invisible" << endl;
cvt->setVisible( false );
}
else
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Setting visible" << endl;
cvt->setVisible( true );
}
cvt->go( true );
c = d->connections.connectionForFamily( 0x0002 );
if ( !c )
return;
SendDCInfoTask* sdcit = new SendDCInfoTask( c->rootTask(), status );
sdcit->go( true ); //autodelete
// TODO: send away message
}
else
{
d->connectAsStatus = status;
d->connectWithMessage = message;
}
}
UserDetails Client::ourInfo() const
{
return d->ourDetails;
}
TQString Client::host()
{
return d->host;
}
int Client::port()
{
return d->port;
}
SSIManager* Client::ssiManager() const
{
return d->ssiManager;
}
const Oscar::ClientVersion* Client::version() const
{
return d->version;
}
// SLOTS //
void Client::streamConnected()
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << endl;
d->stage = ClientPrivate::StageTwo;
if ( m_loginTaskTwo )
m_loginTaskTwo->go();
}
void Client::lt_loginFinished()
{
/* Check for stage two login first, since we create the stage two
* task when we finish stage one
*/
if ( d->stage == ClientPrivate::StageTwo )
{
//we've finished logging in. start the services setup
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "stage two done. setting up services" << endl;
initializeStaticTasks();
ServiceSetupTask* ssTask = new ServiceSetupTask( d->connections.defaultConnection()->rootTask() );
connect( ssTask, TQ_SIGNAL( finished() ), this, TQ_SLOT( serviceSetupFinished() ) );
ssTask->go( true ); //fire and forget
m_loginTaskTwo->deleteLater();
m_loginTaskTwo = 0;
}
else if ( d->stage == ClientPrivate::StageOne )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "stage one login done" << endl;
disconnect( m_loginTask, TQ_SIGNAL( finished() ), this, TQ_SLOT( lt_loginFinished() ) );
if ( m_loginTask->statusCode() == 0 ) //we can start stage two
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "no errors from stage one. moving to stage two" << endl;
//cache these values since they'll be deleted when we close the connections (which deletes the tasks)
d->host = m_loginTask->bosServer();
d->port = m_loginTask->bosPort().toUInt();
d->cookie = m_loginTask->loginCookie();
close();
TQTimer::singleShot( 100, this, TQ_SLOT(startStageTwo() ) );
}
else
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "errors reported. not moving to stage two" << endl;
close(); //deletes the connections for us
}
m_loginTask->deleteLater();
m_loginTask = 0;
}
}
void Client::startStageTwo()
{
//create a new connection and set it up
Connection* c = createConnection( d->host, TQString::number( d->port ) );
new CloseConnectionTask( c->rootTask() );
//create the new login task
m_loginTaskTwo = new StageTwoLoginTask( c->rootTask() );
m_loginTaskTwo->setCookie( d->cookie );
TQObject::connect( m_loginTaskTwo, TQ_SIGNAL( finished() ), this, TQ_SLOT( lt_loginFinished() ) );
//connect
TQObject::connect( c, TQ_SIGNAL( connected() ), this, TQ_SLOT( streamConnected() ) );
connectToServer( c, d->host, false ) ;
}
void Client::serviceSetupFinished()
{
d->active = true;
if ( isIcq() )
setStatus( d->connectAsStatus, d->connectWithMessage );
d->ownStatusTask->go();
if ( isIcq() )
{
//retrieve offline messages
Connection* c = d->connections.connectionForFamily( 0x0015 );
if ( !c ) {
return;
}
OfflineMessagesTask *offlineMsgTask = new OfflineMessagesTask( c->rootTask() );
connect( offlineMsgTask, TQ_SIGNAL( receivedOfflineMessage(const Oscar::Message& ) ),
this, TQ_SIGNAL( messageReceived(const Oscar::Message& ) ) );
offlineMsgTask->go( true );
}
emit haveSSIList();
emit loggedIn();
}
void Client::receivedIcqInfo( const TQString& contact, unsigned int type )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "received icq info for " << contact
<< " of type " << type << endl;
if ( type == ICQUserInfoRequestTask::Short )
emit receivedIcqShortInfo( contact );
else
emit receivedIcqLongInfo( contact );
}
void Client::receivedInfo( TQ_UINT16 sequence )
{
UserDetails details = d->userInfoTask->getInfoFor( sequence );
emit receivedUserInfo( details.userId(), details );
}
void Client::offlineUser( const TQString& user, const UserDetails& )
{
emit userIsOffline( user );
}
void Client::haveOwnUserInfo()
{
kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << endl;
UserDetails ud = d->ownStatusTask->getInfo();
d->ourDetails = ud;
emit haveOwnInfo();
}
void Client::setCodecProvider( Client::CodecProvider* codecProvider )
{
d->codecProvider = codecProvider;
}
void Client::setVersion( const Oscar::ClientVersion* version )
{
d->version = version;
}
// INTERNALS //
TQString Client::userId() const
{
return d->user;
}
TQString Client::password() const
{
return d->pass;
}
TQString Client::statusMessage() const
{
return d->statusMessage;
}
void Client::setStatusMessage( const TQString &message )
{
d->statusMessage = message;
}
TQCString Client::ipAddress() const
{
//!TODO determine ip address
return "127.0.0.1";
}
void Client::notifyTaskError( const Oscar::SNAC& s, int errCode, bool fatal )
{
emit taskError( s, errCode, fatal );
}
void Client::notifySocketError( int errCode, const TQString& msg )
{
emit socketError( errCode, msg );
}
void Client::sendMessage( const Oscar::Message& msg, bool isAuto)
{
Connection* c = 0L;
if ( msg.type() == 0x0003 )
{
c = d->connections.connectionForChatRoom( msg.exchange(), msg.chatRoom() );
if ( !c )
return;
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "sending message to chat room" << endl;
ChatServiceTask* cst = new ChatServiceTask( c->rootTask(), msg.exchange(), msg.chatRoom() );
cst->setMessage( msg );
cst->setEncoding( d->codecProvider->codecForAccount()->name() );
cst->go( true );
}
else
{
c = d->connections.connectionForFamily( 0x0004 );
if ( !c )
return;
SendMessageTask *sendMsgTask = new SendMessageTask( c->rootTask() );
// Set whether or not the message is an automated response
sendMsgTask->setAutoResponse( isAuto );
sendMsgTask->setMessage( msg );
sendMsgTask->go( true );
}
}
void Client::receivedMessage( const Oscar::Message& msg )
{
if ( msg.type() == 2 && !msg.hasProperty( Oscar::Message::AutoResponse ) )
{
// type 2 message needs an autoresponse, regardless of type
Connection* c = d->connections.connectionForFamily( 0x0004 );
if ( !c )
return;
Oscar::Message response ( msg );
if ( msg.hasProperty( Oscar::Message::StatusMessageRequest ) )
{
TQTextCodec* codec = d->codecProvider->codecForContact( msg.sender() );
response.setText( Oscar::Message::UserDefined, statusMessage(), codec );
}
else
{
response.setEncoding( Oscar::Message::UserDefined );
response.setTextArray( TQByteArray() );
}
response.setReceiver( msg.sender() );
response.addProperty( Oscar::Message::AutoResponse );
SendMessageTask *sendMsgTask = new SendMessageTask( c->rootTask() );
sendMsgTask->setMessage( response );
sendMsgTask->go( true );
}
if ( msg.hasProperty( Oscar::Message::StatusMessageRequest ) )
{
if ( msg.hasProperty( Oscar::Message::AutoResponse ) )
{
// we got a response to a status message request.
TQString awayMessage( msg.text( d->codecProvider->codecForContact( msg.sender() ) ) );
kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Received an away message: " << awayMessage << endl;
emit receivedAwayMessage( msg.sender(), awayMessage );
}
}
else if ( ! msg.hasProperty( Oscar::Message::AutoResponse ) )
{
// Filter out miranda's invisible check
if ( msg.messageType() == 0x0004 && msg.textArray().isEmpty() )
return;
// let application handle it
kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Emitting receivedMessage" << endl;
emit messageReceived( msg );
}
}
void Client::requestAuth( const TQString& contactid, const TQString& reason )
{
Connection* c = d->connections.connectionForFamily( 0x0013 );
if ( !c )
return;
d->ssiAuthTask->sendAuthRequest( contactid, reason );
}
void Client::sendAuth( const TQString& contactid, const TQString& reason, bool auth )
{
Connection* c = d->connections.connectionForFamily( 0x0013 );
if ( !c )
return;
d->ssiAuthTask->sendAuthReply( contactid, reason, auth );
}
bool Client::isActive() const
{
return d->active;
}
bool Client::isIcq() const
{
return d->isIcq;
}
void Client::setIsIcq( bool isIcq )
{
d->isIcq = isIcq;
}
void Client::debug( const TQString& str )
{
Q_UNUSED(str);
// tqDebug( "CLIENT: %s", str.ascii() );
}
void Client::initializeStaticTasks()
{
//set up the extra tasks
Connection* c = d->connections.defaultConnection();
if ( !c )
return;
d->errorTask = new ErrorTask( c->rootTask() );
d->onlineNotifier = new OnlineNotifierTask( c->rootTask() );
d->ownStatusTask = new OwnUserInfoTask( c->rootTask() );
d->messageReceiverTask = new MessageReceiverTask( c->rootTask() );
d->ssiAuthTask = new SSIAuthTask( c->rootTask() );
d->icqInfoTask = new ICQUserInfoRequestTask( c->rootTask() );
d->userInfoTask = new UserInfoTask( c->rootTask() );
d->typingNotifyTask = new TypingNotifyTask( c->rootTask() );
d->ssiModifyTask = new SSIModifyTask( c->rootTask(), true );
connect( d->onlineNotifier, TQ_SIGNAL( userIsOnline( const TQString&, const UserDetails& ) ),
this, TQ_SIGNAL( receivedUserInfo( const TQString&, const UserDetails& ) ) );
connect( d->onlineNotifier, TQ_SIGNAL( userIsOffline( const TQString&, const UserDetails& ) ),
this, TQ_SLOT( offlineUser( const TQString&, const UserDetails & ) ) );
connect( d->ownStatusTask, TQ_SIGNAL( gotInfo() ), this, TQ_SLOT( haveOwnUserInfo() ) );
connect( d->ownStatusTask, TQ_SIGNAL( buddyIconUploadRequested() ), this,
TQ_SIGNAL( iconNeedsUploading() ) );
connect( d->messageReceiverTask, TQ_SIGNAL( receivedMessage( const Oscar::Message& ) ),
this, TQ_SLOT( receivedMessage( const Oscar::Message& ) ) );
connect( d->ssiAuthTask, TQ_SIGNAL( authRequested( const TQString&, const TQString& ) ),
this, TQ_SIGNAL( authRequestReceived( const TQString&, const TQString& ) ) );
connect( d->ssiAuthTask, TQ_SIGNAL( authReplied( const TQString&, const TQString&, bool ) ),
this, TQ_SIGNAL( authReplyReceived( const TQString&, const TQString&, bool ) ) );
connect( d->icqInfoTask, TQ_SIGNAL( receivedInfoFor( const TQString&, unsigned int ) ),
this, TQ_SLOT( receivedIcqInfo( const TQString&, unsigned int ) ) );
connect( d->userInfoTask, TQ_SIGNAL( receivedProfile( const TQString&, const TQString& ) ),
this, TQ_SIGNAL( receivedProfile( const TQString&, const TQString& ) ) );
connect( d->userInfoTask, TQ_SIGNAL( receivedAwayMessage( const TQString&, const TQString& ) ),
this, TQ_SIGNAL( receivedAwayMessage( const TQString&, const TQString& ) ) );
connect( d->typingNotifyTask, TQ_SIGNAL( typingStarted( const TQString& ) ),
this, TQ_SIGNAL( userStartedTyping( const TQString& ) ) );
connect( d->typingNotifyTask, TQ_SIGNAL( typingFinished( const TQString& ) ),
this, TQ_SIGNAL( userStoppedTyping( const TQString& ) ) );
}
void Client::removeGroup( const TQString& groupName )
{
Connection* c = d->connections.connectionForFamily( 0x0013 );
if ( !c )
return;
kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Removing group " << groupName << " from SSI" << endl;
SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
if ( ssimt->removeGroup( groupName ) )
ssimt->go( true );
else
delete ssimt;
}
void Client::addGroup( const TQString& groupName )
{
Connection* c = d->connections.connectionForFamily( 0x0013 );
if ( !c )
return;
kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Adding group " << groupName << " to SSI" << endl;
SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
if ( ssimt->addGroup( groupName ) )
ssimt->go( true );
else
delete ssimt;
}
void Client::addContact( const TQString& contactName, const TQString& groupName )
{
Connection* c = d->connections.connectionForFamily( 0x0013 );
if ( !c )
return;
kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Adding contact " << contactName << " to SSI in group " << groupName << endl;
SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
if ( ssimt->addContact( contactName, groupName ) )
ssimt->go( true );
else
delete ssimt;
}
void Client::removeContact( const TQString& contactName )
{
Connection* c = d->connections.connectionForFamily( 0x0013 );
if ( !c )
return;
kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Removing contact " << contactName << " from SSI" << endl;
SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
if ( ssimt->removeContact( contactName ) )
ssimt->go( true );
else
delete ssimt;
}
void Client::renameGroup( const TQString & oldGroupName, const TQString & newGroupName )
{
Connection* c = d->connections.connectionForFamily( 0x0013 );
if ( !c )
return;
kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Renaming group " << oldGroupName << " to " << newGroupName << endl;
SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
if ( ssimt->renameGroup( oldGroupName, newGroupName ) )
ssimt->go( true );
else
delete ssimt;
}
void Client::modifySSIItem( const Oscar::SSI& oldItem, const Oscar::SSI& newItem )
{
int action = 0; //0 modify, 1 add, 2 remove TODO cleanup!
Connection* c = d->connections.connectionForFamily( 0x0013 );
if ( !c )
return;
if ( !oldItem && newItem )
action = 1;
if ( oldItem && !newItem )
action = 2;
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Add/Mod/Del item on server" << endl;
SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
switch ( action )
{
case 0:
if ( ssimt->modifyItem( oldItem, newItem ) )
ssimt->go( true );
else
delete ssimt;
break;
case 1:
if ( ssimt->addItem( newItem ) )
ssimt->go( true );
else
delete ssimt;
break;
case 2:
if ( ssimt->removeItem( oldItem ) )
ssimt->go( true );
else
delete ssimt;
break;
}
}
void Client::changeContactGroup( const TQString& contact, const TQString& newGroupName )
{
Connection* c = d->connections.connectionForFamily( 0x0013 );
if ( !c )
return;
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Changing " << contact << "'s group to "
<< newGroupName << endl;
SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
if ( ssimt->changeGroup( contact, newGroupName ) )
ssimt->go( true );
else
delete ssimt;
}
void Client::requestFullInfo( const TQString& contactId )
{
Connection* c = d->connections.connectionForFamily( 0x0015 );
if ( !c )
return;
d->icqInfoTask->setUser( contactId );
d->icqInfoTask->setType( ICQUserInfoRequestTask::Long );
d->icqInfoTask->go();
}
void Client::requestShortInfo( const TQString& contactId )
{
Connection* c = d->connections.connectionForFamily( 0x0015 );
if ( !c )
return;
d->icqInfoTask->setUser( contactId );
d->icqInfoTask->setType( ICQUserInfoRequestTask::Short );
d->icqInfoTask->go();
}
void Client::sendWarning( const TQString& contact, bool anonymous )
{
Connection* c = d->connections.connectionForFamily( 0x0004 );
if ( !c )
return;
WarningTask* warnTask = new WarningTask( c->rootTask() );
warnTask->setContact( contact );
warnTask->setAnonymous( anonymous );
TQObject::connect( warnTask, TQ_SIGNAL( userWarned( const TQString&, TQ_UINT16, TQ_UINT16 ) ),
this, TQ_SIGNAL( userWarned( const TQString&, TQ_UINT16, TQ_UINT16 ) ) );
warnTask->go( true );
}
ICQGeneralUserInfo Client::getGeneralInfo( const TQString& contact )
{
return d->icqInfoTask->generalInfoFor( contact );
}
ICQWorkUserInfo Client::getWorkInfo( const TQString& contact )
{
return d->icqInfoTask->workInfoFor( contact );
}
ICQEmailInfo Client::getEmailInfo( const TQString& contact )
{
return d->icqInfoTask->emailInfoFor( contact );
}
ICQMoreUserInfo Client::getMoreInfo( const TQString& contact )
{
return d->icqInfoTask->moreInfoFor( contact );
}
ICQInterestInfo Client::getInterestInfo( const TQString& contact )
{
return d->icqInfoTask->interestInfoFor( contact );
}
ICQShortInfo Client::getShortInfo( const TQString& contact )
{
return d->icqInfoTask->shortInfoFor( contact );
}
TQValueList<int> Client::chatExchangeList() const
{
return d->exchanges;
}
void Client::setChatExchangeList( const TQValueList<int>& exchanges )
{
d->exchanges = exchanges;
}
void Client::requestAIMProfile( const TQString& contact )
{
d->userInfoTask->requestInfoFor( contact, UserInfoTask::Profile );
}
void Client::requestAIMAwayMessage( const TQString& contact )
{
d->userInfoTask->requestInfoFor( contact, UserInfoTask::AwayMessage );
}
void Client::requestICQAwayMessage( const TQString& contact, ICQStatus contactStatus )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "requesting away message for " << contact << endl;
Oscar::Message msg;
msg.setType( 2 );
msg.setReceiver( contact );
msg.addProperty( Oscar::Message::StatusMessageRequest );
switch ( contactStatus )
{
case ICQAway:
msg.setMessageType( 0xE8 ); // away
break;
case ICQOccupied:
msg.setMessageType( 0xE9 ); // occupied
break;
case ICQNotAvailable:
msg.setMessageType( 0xEA ); // not awailable
break;
case ICQDoNotDisturb:
msg.setMessageType( 0xEB ); // do not disturb
break;
case ICQFreeForChat:
msg.setMessageType( 0xEC ); // free for chat
break;
default:
// may be a good way to deal with possible error and lack of online status message?
emit receivedAwayMessage( contact, "Sorry, this protocol does not support this type of status message" );
return;
}
sendMessage( msg );
}
void Client::addICQAwayMessageRequest( const TQString& contact, ICQStatus contactStatus )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "adding away message request for "
<< contact << " to queue" << endl;
//remove old request if still exists
removeICQAwayMessageRequest( contact );
ClientPrivate::AwayMsgRequest amr = { contact, contactStatus };
d->awayMsgRequestQueue.prepend( amr );
if ( !d->awayMsgRequestTimer->isActive() )
d->awayMsgRequestTimer->start( 1000 );
}
void Client::removeICQAwayMessageRequest( const TQString& contact )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "removing away message request for "
<< contact << " from queue" << endl;
TQValueList<ClientPrivate::AwayMsgRequest>::iterator it = d->awayMsgRequestQueue.begin();
while ( it != d->awayMsgRequestQueue.end() )
{
if ( (*it).contact == contact )
it = d->awayMsgRequestQueue.erase( it );
else
it++;
}
}
void Client::nextICQAwayMessageRequest()
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "request queue count " << d->awayMsgRequestQueue.count() << endl;
if ( d->awayMsgRequestQueue.empty() )
{
d->awayMsgRequestTimer->stop();
return;
}
else
{
Connection* c = d->connections.connectionForFamily( 0x0004 );
if ( !c )
return;
SNAC s = { 0x0004, 0x0006, 0x0000, 0x00000000 };
//get time needed to restore level to initial
//for some reason when we are long under initial level
//icq server will start to block our messages
int time = c->rateManager()->timeToInitialLevel( s );
if ( time > 0 )
{
d->awayMsgRequestTimer->changeInterval( time );
return;
}
else
{
d->awayMsgRequestTimer->changeInterval( 5000 );
}
}
ClientPrivate::AwayMsgRequest amr;
amr = d->awayMsgRequestQueue.back();
d->awayMsgRequestQueue.pop_back();
requestICQAwayMessage( amr.contact, amr.contactStatus );
}
void Client::requestStatusInfo( const TQString& contact )
{
d->userInfoTask->requestInfoFor( contact, UserInfoTask::General );
}
void Client::whitePagesSearch( const ICQWPSearchInfo& info )
{
Connection* c = d->connections.connectionForFamily( 0x0015 );
if ( !c )
return;
UserSearchTask* ust = new UserSearchTask( c->rootTask() );
connect( ust, TQ_SIGNAL( foundUser( const ICQSearchResult& ) ),
this, TQ_SIGNAL( gotSearchResults( const ICQSearchResult& ) ) );
connect( ust, TQ_SIGNAL( searchFinished( int ) ), this, TQ_SIGNAL( endOfSearch( int ) ) );
ust->go( true ); //onGo does nothing in this task. This is just here so autodelete works
ust->searchWhitePages( info );
}
void Client::uinSearch( const TQString& uin )
{
Connection* c = d->connections.connectionForFamily( 0x0015 );
if ( !c )
return;
UserSearchTask* ust = new UserSearchTask( c->rootTask() );
connect( ust, TQ_SIGNAL( foundUser( const ICQSearchResult& ) ),
this, TQ_SIGNAL( gotSearchResults( const ICQSearchResult& ) ) );
connect( ust, TQ_SIGNAL( searchFinished( int ) ), this, TQ_SIGNAL( endOfSearch( int ) ) );
ust->go( true ); //onGo does nothing in this task. This is just here so autodelete works
ust->searchUserByUIN( uin );
}
void Client::updateProfile( const TQString& profile )
{
Connection* c = d->connections.connectionForFamily( 0x0002 );
if ( !c ) {
return;
}
ProfileTask* pt = new ProfileTask( c->rootTask() );
pt->setProfileText( profile );
pt->go(true);
}
void Client::sendTyping( const TQString & contact, bool typing )
{
Connection* c = d->connections.connectionForFamily( 0x0004 );
if ( !c )
return;
d->typingNotifyTask->setParams( contact, ( typing ? TypingNotifyTask::Begin : TypingNotifyTask::Finished ) );
d->typingNotifyTask->go( false ); // don't delete the task after sending
}
void Client::connectToIconServer()
{
Connection* c = d->connections.connectionForFamily( 0x0010 );
if ( c )
return;
requestServerRedirect( 0x0010 );
return;
}
void Client::setIgnore( const TQString& user, bool ignore )
{
Oscar::SSI item = ssiManager()->findItem( user, ROSTER_IGNORE );
if ( item && !ignore )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Removing " << user << " from ignore list" << endl;
this->modifySSIItem( item, Oscar::SSI() );
}
else if ( !item && ignore )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Adding " << user << " to ignore list" << endl;
Oscar::SSI s( user, 0, ssiManager()->nextContactId(), ROSTER_IGNORE, TQValueList<TLV>() );
this->modifySSIItem( Oscar::SSI(), s );
}
}
void Client::setVisibleTo( const TQString& user, bool visible )
{
Oscar::SSI item = ssiManager()->findItem( user, ROSTER_VISIBLE );
if ( item && !visible )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Removing " << user << " from visible list" << endl;
this->modifySSIItem( item, Oscar::SSI() );
}
else if ( !item && visible )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Adding " << user << " to visible list" << endl;
Oscar::SSI s( user, 0, ssiManager()->nextContactId(), ROSTER_VISIBLE, TQValueList<TLV>() );
this->modifySSIItem( Oscar::SSI(), s );
}
}
void Client::setInvisibleTo( const TQString& user, bool invisible )
{
Oscar::SSI item = ssiManager()->findItem( user, ROSTER_INVISIBLE );
if ( item && !invisible )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Removing " << user << " from invisible list" << endl;
this->modifySSIItem( item, Oscar::SSI() );
}
else if ( !item && invisible )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Adding " << user << " to invisible list" << endl;
Oscar::SSI s( user, 0, ssiManager()->nextContactId(), ROSTER_INVISIBLE, TQValueList<TLV>() );
this->modifySSIItem( Oscar::SSI(), s );
}
}
void Client::requestBuddyIcon( const TQString& user, const TQByteArray& hash, BYTE hashType )
{
Connection* c = d->connections.connectionForFamily( 0x0010 );
if ( !c )
return;
BuddyIconTask* bit = new BuddyIconTask( c->rootTask() );
connect( bit, TQ_SIGNAL( haveIcon( const TQString&, TQByteArray ) ),
this, TQ_SIGNAL( haveIconForContact( const TQString&, TQByteArray ) ) );
bit->requestIconFor( user );
bit->setHashType( hashType );
bit->setHash( hash );
bit->go( true );
}
void Client::requestServerRedirect( WORD family, WORD exchange,
TQByteArray cookie, WORD instance,
const TQString& room )
{
//making the assumption that family 2 will always be the BOS connection
//use it instead since we can't query for family 1
Connection* c = d->connections.connectionForFamily( family );
if ( c && family != 0x000E )
return; //we already have the connection
c = d->connections.connectionForFamily( 0x0002 );
if ( !c )
return;
if ( d->redirectionServices.findIndex( family ) == -1 )
d->redirectionServices.append( family ); //don't add families twice
if ( d->currentRedirect != 0 )
return; //we're already doing one redirection
d->currentRedirect = family;
//FIXME. this won't work if we have to defer the connection because we're
//already connecting to something
ServerRedirectTask* srt = new ServerRedirectTask( c->rootTask() );
if ( family == 0x000E )
{
srt->setChatParams( exchange, cookie, instance );
srt->setChatRoom( room );
}
connect( srt, TQ_SIGNAL( haveServer( const TQString&, const TQByteArray&, WORD ) ),
this, TQ_SLOT( haveServerForRedirect( const TQString&, const TQByteArray&, WORD ) ) );
srt->setService( family );
srt->go( true );
}
void Client::haveServerForRedirect( const TQString& host, const TQByteArray& cookie, WORD )
{
//nasty sender() usage to get the task with chat room info
ServerRedirectTask* srt = dynamic_cast<ServerRedirectTask*>( const_cast<TQObject*>(sender()) );
//create a new connection and set it up
int colonPos = host.find(':');
TQString realHost, realPort;
if ( colonPos != -1 )
{
realHost = host.left( colonPos );
realPort = host.right(4); //we only need 4 bytes
}
else
{
realHost = host;
realPort = TQString::fromLatin1("5190");
}
Connection* c = createConnection( realHost, realPort );
//create the new login task
m_loginTaskTwo = new StageTwoLoginTask( c->rootTask() );
m_loginTaskTwo->setCookie( cookie );
TQObject::connect( m_loginTaskTwo, TQ_SIGNAL( finished() ), this, TQ_SLOT( serverRedirectFinished() ) );
//connect
connectToServer( c, d->host, false );
TQObject::connect( c, TQ_SIGNAL( connected() ), this, TQ_SLOT( streamConnected() ) );
if ( srt )
d->connections.addChatInfoForConnection( c, srt->chatExchange(), srt->chatRoomName() );
}
void Client::serverRedirectFinished()
{
if ( m_loginTaskTwo->statusCode() == 0 )
{ //stage two was successful
Connection* c = d->connections.connectionForFamily( d->currentRedirect );
if ( !c )
return;
ClientReadyTask* crt = new ClientReadyTask( c->rootTask() );
crt->setFamilies( c->supportedFamilies() );
crt->go( true );
}
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "redirection finished for service "
<< d->currentRedirect << endl;
if ( d->currentRedirect == 0x0010 )
emit iconServerConnected();
if ( d->currentRedirect == 0x000D )
{
connect( this, TQ_SIGNAL( chatNavigationConnected() ),
this, TQ_SLOT( requestChatNavLimits() ) );
emit chatNavigationConnected();
}
if ( d->currentRedirect == 0x000E )
{
//HACK! such abuse! think of a better way
if ( !m_loginTaskTwo )
{
kdWarning(OSCAR_RAW_DEBUG) << k_funcinfo << "no login task to get connection from!" << endl;
emit redirectionFinished( d->currentRedirect );
return;
}
Connection* c = m_loginTaskTwo->client();
TQString roomName = d->connections.chatRoomForConnection( c );
WORD exchange = d->connections.exchangeForConnection( c );
if ( c )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "setting up chat connection" << endl;
ChatServiceTask* cst = new ChatServiceTask( c->rootTask(), exchange, roomName );
connect( cst, TQ_SIGNAL( userJoinedChat( Oscar::WORD, const TQString&, const TQString& ) ),
this, TQ_SIGNAL( userJoinedChat( Oscar::WORD, const TQString&, const TQString& ) ) );
connect( cst, TQ_SIGNAL( userLeftChat( Oscar::WORD, const TQString&, const TQString& ) ),
this, TQ_SIGNAL( userLeftChat( Oscar::WORD, const TQString&, const TQString& ) ) );
connect( cst, TQ_SIGNAL( newChatMessage( const Oscar::Message& ) ),
this, TQ_SIGNAL( messageReceived( const Oscar::Message& ) ) );
}
emit chatRoomConnected( exchange, roomName );
}
emit redirectionFinished( d->currentRedirect );
}
void Client::checkRedirectionQueue( WORD family )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "checking redirection queue" << endl;
d->redirectionServices.remove( family );
d->currentRedirect = 0;
if ( !d->redirectionServices.isEmpty() )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "scheduling new redirection" << endl;
requestServerRedirect( d->redirectionServices.front() );
}
}
void Client::requestChatNavLimits()
{
Connection* c = d->connections.connectionForFamily( 0x000D );
if ( !c )
return;
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "requesting chat nav service limits" << endl;
ChatNavServiceTask* cnst = new ChatNavServiceTask( c->rootTask() );
cnst->setRequestType( ChatNavServiceTask::Limits );
TQObject::connect( cnst, TQ_SIGNAL( haveChatExchanges( const TQValueList<int>& ) ),
this, TQ_SLOT( setChatExchangeList( const TQValueList<int>& ) ) );
cnst->go( true ); //autodelete
}
void Client::determineDisconnection( int code, const TQString& string )
{
if ( !sender() )
return;
//yay for the sender() hack!
Connection* c = dynamic_cast<Connection*>( const_cast<TQObject*>(sender()) );
if ( !c )
return;
if ( c->isSupported( 0x0002 ) ||
d->stage == ClientPrivate::StageOne ) //emit on login
{
emit socketError( code, string );
}
//connection is deleted. deleteLater() is used
d->connections.remove( c );
c = 0;
}
void Client::sendBuddyIcon( const TQByteArray& iconData )
{
Connection* c = d->connections.connectionForFamily( 0x0010 );
if ( !c )
return;
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "icon length is " << iconData.size() << endl;
BuddyIconTask* bit = new BuddyIconTask( c->rootTask() );
bit->uploadIcon( iconData.size(), iconData );
bit->go( true );
}
void Client::joinChatRoom( const TQString& roomName, int exchange )
{
Connection* c = d->connections.connectionForFamily( 0x000D );
if ( !c )
return;
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "joining the chat room '" << roomName
<< "' on exchange " << exchange << endl;
ChatNavServiceTask* cnst = new ChatNavServiceTask( c->rootTask() );
connect( cnst, TQ_SIGNAL( connectChat( WORD, TQByteArray, WORD, const TQString& ) ),
this, TQ_SLOT( setupChatConnection( WORD, TQByteArray, WORD, const TQString& ) ) );
cnst->createRoom( exchange, roomName );
}
void Client::setupChatConnection( WORD exchange, TQByteArray cookie, WORD instance, const TQString& room )
{
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "cookie is:" << cookie << endl;
TQByteArray realCookie( cookie );
kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "connection to chat room" << endl;
requestServerRedirect( 0x000E, exchange, realCookie, instance, room );
}
void Client::disconnectChatRoom( WORD exchange, const TQString& room )
{
Connection* c = d->connections.connectionForChatRoom( exchange, room );
if ( !c )
return;
d->connections.remove( c );
c = 0;
}
Connection* Client::createConnection( const TQString& host, const TQString& port )
{
KNetworkConnector* knc = new KNetworkConnector( 0 );
knc->setOptHostPort( host, port.toUInt() );
ClientStream* cs = new ClientStream( knc, 0 );
cs->setNoopTime( 60000 );
Connection* c = new Connection( knc, cs, "BOS" );
cs->setConnection( c );
c->setClient( this );
return c;
}
void Client::deleteStaticTasks()
{
delete d->errorTask;
delete d->onlineNotifier;
delete d->ownStatusTask;
delete d->messageReceiverTask;
delete d->ssiAuthTask;
delete d->icqInfoTask;
delete d->userInfoTask;
delete d->typingNotifyTask;
delete d->ssiModifyTask;
d->errorTask = 0;
d->onlineNotifier = 0;
d->ownStatusTask = 0;
d->messageReceiverTask = 0;
d->ssiAuthTask = 0;
d->icqInfoTask = 0;
d->userInfoTask = 0;
d->typingNotifyTask = 0;
d->ssiModifyTask = 0;
}
bool Client::hasIconConnection( ) const
{
Connection* c = d->connections.connectionForFamily( 0x0010 );
return c;
}
#include "client.moc"