/* msnchatsession.cpp - MSN Message Manager Copyright (c) 2002-2005 by Olivier Goffart Kopete (c) 2002-2005 by the Kopete developers ************************************************************************* * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ************************************************************************* */ #include "msnchatsession.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kopetecontactaction.h" #include "kopetemetacontact.h" #include "kopetecontactlist.h" #include "kopetechatsessionmanager.h" #include "kopeteuiglobal.h" #include "kopeteglobal.h" #include "kopeteview.h" #include "msncontact.h" #include "msnfiletransfersocket.h" #include "msnaccount.h" #include "msnswitchboardsocket.h" #include "config.h" #if !defined NDEBUG #include "msndebugrawcmddlg.h" #endif MSNChatSession::MSNChatSession( Kopete::Protocol *protocol, const Kopete::Contact *user, Kopete::ContactPtrList others, const char *name ) : Kopete::ChatSession( user, others, protocol, name ) { Kopete::ChatSessionManager::self()->registerChatSession( this ); m_chatService = 0l; m_timeoutTimer =0L; m_newSession = true; m_connectionTry=0; setInstance(protocol->instance()); connect( this, TQ_SIGNAL( messageSent( Kopete::Message&, Kopete::ChatSession* ) ), this, TQ_SLOT( slotMessageSent( Kopete::Message&, Kopete::ChatSession* ) ) ); connect( this, TQ_SIGNAL( invitation(MSNInvitation*& , const TQString & , long unsigned int , MSNChatSession* , MSNContact* ) ) , protocol, TQ_SIGNAL( invitation(MSNInvitation*& , const TQString & , long unsigned int , MSNChatSession* , MSNContact* ) ) ); m_actionInvite = new TDEActionMenu( i18n( "&Invite" ), "kontact_contacts", actionCollection(), "msnInvite" ); connect ( m_actionInvite->popupMenu() , TQ_SIGNAL( aboutToShow() ) , this , TQ_SLOT(slotActionInviteAboutToShow() ) ) ; #if !defined NDEBUG new TDEAction( i18n( "Send Raw C&ommand..." ), 0, this, TQ_SLOT( slotDebugRawCommand() ), actionCollection(), "msnDebugRawCommand" ) ; #endif m_actionNudge=new TDEAction( i18n( "Send Nudge" ), "bell", 0, this, TQ_SLOT(slotSendNudge() ), actionCollection(), "msnSendNudge" ) ; #if MSN_WEBCAM // Invite to receive webcam action m_actionWebcamReceive=new TDEAction( i18n( "View Contact's Webcam" ), "webcamreceive", 0, this, TQ_SLOT(slotWebcamReceive()), actionCollection(), "msnWebcamReceive" ) ; //Send webcam action m_actionWebcamSend=new TDEAction( i18n( "Send Webcam" ), "webcamsend", 0, this, TQ_SLOT(slotWebcamSend()), actionCollection(), "msnWebcamSend" ) ; #endif new TDEAction( i18n( "Send File" ),"attach", 0, this, TQ_SLOT( slotSendFile() ), actionCollection(), "msnSendFile" ); MSNContact *c = static_cast( others.first() ); (new TDEAction( i18n( "Request Display Picture" ), "image", 0, this, TQ_SLOT( slotRequestPicture() ), actionCollection(), "msnRequestDisplayPicture" ))->setEnabled(!c->object().isEmpty()); if ( !c->object().isEmpty() ) { connect( c, TQ_SIGNAL( displayPictureChanged() ), this, TQ_SLOT( slotDisplayPictureChanged() ) ); m_image = new TQLabel( 0L, "kde toolbar widget" ); new KWidgetAction( m_image, i18n( "MSN Display Picture" ), 0, this, TQ_SLOT( slotRequestPicture() ), actionCollection(), "msnDisplayPicture" ); if(c->hasProperty(Kopete::Global::Properties::self()->photo().key()) ) { //if the view doesn't exist yet, we will be unable to get the size of the toolbar // so when the view will exist, we will show the displaypicture. //How to know when a our view is created? We can't. // but chances are the next created view will be for this KMM // And if it is not? never mind. the icon will just be sized 22x22 connect( Kopete::ChatSessionManager::self() , TQ_SIGNAL(viewActivated(KopeteView* )) , this, TQ_SLOT(slotDisplayPictureChanged()) ); //it's viewActivated and not viewCreated because the view get his mainwindow only when it is shown. } } else { m_image = 0L; } setXMLFile("msnchatui.rc"); setMayInvite( true ); } MSNChatSession::~MSNChatSession() { delete m_image; //force to disconnect the switchboard //not needed since the m_chatService has us as parent // if(m_chatService) // delete m_chatService; TQMap::Iterator it; for( it = m_invitations.begin(); it != m_invitations.end() ; it = m_invitations.begin()) { delete *it; m_invitations.remove( it ); } } void MSNChatSession::createChat( const TQString &handle, const TQString &address, const TQString &auth, const TQString &ID ) { /* disabled because i don't want to reopen a chatwindow if we just closed it * and the contact take much time to type his message m_newSession= !(ID.isEmpty()); */ if( m_chatService ) { kdDebug(14140) << k_funcinfo << "Service already exists, disconnect them." << endl; delete m_chatService; } // uncomment this line if you don't want to the peer know when you close the window // setCanBeDeleted( false ); m_chatService = new MSNSwitchBoardSocket( static_cast( myself()->account() ) , this); m_chatService->setUseHttpMethod( static_cast( myself()->account() )->useHttpMethod() ); m_chatService->setHandle( myself()->account()->accountId() ); m_chatService->setMsgHandle( handle ); m_chatService->connectToSwitchBoard( ID, address, auth ); connect( m_chatService, TQ_SIGNAL( userJoined(const TQString&,const TQString&,bool)), this, TQ_SLOT( slotUserJoined(const TQString&,const TQString&,bool) ) ); connect( m_chatService, TQ_SIGNAL( userLeft(const TQString&,const TQString&)), this, TQ_SLOT( slotUserLeft(const TQString&,const TQString&) ) ); connect( m_chatService, TQ_SIGNAL( msgReceived( Kopete::Message & ) ), this, TQ_SLOT( slotMessageReceived( Kopete::Message & ) ) ); connect( m_chatService, TQ_SIGNAL( switchBoardClosed() ), this, TQ_SLOT( slotSwitchBoardClosed() ) ); connect( m_chatService, TQ_SIGNAL( receivedTypingMsg( const TQString &, bool ) ), this, TQ_SLOT( receivedTypingMsg( const TQString &, bool ) ) ); TDEConfig *config = TDEGlobal::config(); config->setGroup( "MSN" ); if(config->readBoolEntry( "SendTypingNotification" , true) ) { connect( this, TQ_SIGNAL( myselfTyping( bool ) ), m_chatService, TQ_SLOT( sendTypingMsg( bool ) ) ); } connect( m_chatService, TQ_SIGNAL( msgAcknowledgement(unsigned int, bool) ), this, TQ_SLOT( slotAcknowledgement(unsigned int, bool) ) ); connect( m_chatService, TQ_SIGNAL( invitation( const TQString&, const TQString& ) ), this, TQ_SLOT( slotInvitation( const TQString&, const TQString& ) ) ); connect( m_chatService, TQ_SIGNAL( nudgeReceived(const TQString&) ), this, TQ_SLOT( slotNudgeReceived(const TQString&) ) ); connect( m_chatService, TQ_SIGNAL( errorMessage(int, const TQString& ) ), static_cast(myself()->account()), TQ_SLOT( slotErrorMessageReceived(int, const TQString& ) ) ); if(!m_timeoutTimer) { m_timeoutTimer=new TQTimer(this); connect( m_timeoutTimer , TQ_SIGNAL(timeout()), this , TQ_SLOT(slotConnectionTimeout() ) ); } m_timeoutTimer->start(20000,true); } void MSNChatSession::slotUserJoined( const TQString &handle, const TQString &publicName, bool IRO ) { delete m_timeoutTimer; m_timeoutTimer=0L; if( !account()->contacts()[ handle ] ) account()->addContact( handle, TQString(), 0L, Kopete::Account::Temporary); MSNContact *c = static_cast( account()->contacts()[ handle ] ); c->setProperty( Kopete::Global::Properties::self()->nickName() , publicName); if(c->clientFlags() & MSNProtocol::MSNC4 ) { m_actionNudge->setEnabled(true); } #if MSN_WEBCAM if(c->clientFlags() & MSNProtocol::SupportWebcam ) { m_actionWebcamReceive->setEnabled(true); } #endif addContact(c , IRO); // don't show notificaions when we join wesalef if(!m_messagesQueue.empty() || !m_invitations.isEmpty()) sendMessageQueue(); TDEConfig *config = TDEGlobal::config(); config->setGroup( "MSN" ); if ( members().count()==1 && config->readNumEntry( "DownloadPicture", 1 ) >= 1 && !c->object().isEmpty() && !c->hasProperty(Kopete::Global::Properties::self()->photo().key())) slotRequestPicture(); } void MSNChatSession::slotUserLeft( const TQString &handle, const TQString& reason ) { MSNContact *c = static_cast( myself()->account()->contacts()[ handle ] ); if(c) removeContact(c, reason ); } void MSNChatSession::slotSwitchBoardClosed() { //kdDebug(14140) << "MSNChatSession::slotSwitchBoardClosed" << endl; m_chatService->deleteLater(); m_chatService=0l; cleanMessageQueue( i18n("Connection closed") ); if(m_invitations.isEmpty()) setCanBeDeleted( true ); } void MSNChatSession::slotMessageSent(Kopete::Message &message,Kopete::ChatSession *) { m_newSession=false; if(m_chatService) { int id = m_chatService->sendMsg(message); if(id == -1) { m_messagesQueue.append(message); kdDebug(14140) << k_funcinfo << "Message added to the queue" <popupMenu()->clear(); TQDictIterator it( account()->contacts() ); for( ; it.current(); ++it ) { if( !members().contains( it.current() ) && it.current()->isOnline() && it.current() != myself() ) { TDEAction *a=new KopeteContactAction( it.current(), this, TQ_SLOT( slotInviteContact( Kopete::Contact * ) ), m_actionInvite ); m_actionInvite->insert( a ); m_inviteactions.append( a ) ; } } TDEAction *b=new TDEAction( i18n ("Other..."), 0, this, TQ_SLOT( slotInviteOtherContact() ), m_actionInvite, "actionOther" ); m_actionInvite->insert( b ); m_inviteactions.append( b ) ; } void MSNChatSession::slotCloseSession() { kdDebug(14140) << k_funcinfo << m_chatService <slotCloseSession(); } void MSNChatSession::slotInviteContact( Kopete::Contact *contact ) { if(contact) inviteContact( contact->contactId() ); } void MSNChatSession::inviteContact(const TQString &contactId) { if( m_chatService ) m_chatService->slotInviteContact( contactId ); else startChatSession(); } void MSNChatSession::slotInviteOtherContact() { bool ok; TQString handle = KInputDialog::getText(i18n( "MSN Plugin" ), i18n( "Please enter the email address of the person you want to invite:" ), TQString(), &ok ); if( !ok ) return; if( handle.contains('@') != 1 || handle.contains('.') <1) { KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, i18n("You must enter a valid email address."), i18n("MSN Plugin")); return; } inviteContact(handle); } void MSNChatSession::sendMessageQueue() { if(!m_chatService) { kdDebug(14140) <::iterator it = m_messagesQueue.begin(); it!=m_messagesQueue.end(); it = m_messagesQueue.begin() ) { //m_chatService->sendMsg( *it) ; slotMessageSent(*it , this); m_messagesQueue.remove(it); } TQMap::Iterator it; for( it = m_invitations.begin(); it != m_invitations.end() ; ++it) { if(! (*it)->incoming() && (*it)->state()sendCommand( "MSG" , "N", true, (*it)->invitationHead().utf8() ); (*it)->setState(MSNInvitation::Invited); } } } void MSNChatSession::slotAcknowledgement(unsigned int id, bool ack) { if ( !m_messagesSent.contains( id ) ) { // This is maybe a ACK/NAK for a non-messaging message return; } if ( !ack ) { Kopete::Message m = m_messagesSent[ id ]; TQString body = i18n( "The following message has not been sent correctly:\n%1" ).arg( m.plainBody() ); Kopete::Message msg = Kopete::Message( m.to().first(), members(), body, Kopete::Message::Internal, Kopete::Message::PlainText ); appendMessage( msg ); //stop the stupid animation messageSucceeded(); } else { messageSucceeded(); } m_messagesSent.remove( id ); } void MSNChatSession::slotInvitation(const TQString &handle, const TQString &msg) { //FIXME! a contact from another account can send a file MSNContact *c = static_cast( myself()->account()->contacts()[ handle ] ); if(!c) return; TQRegExp rx("Invitation-Cookie: ([0-9]*)"); rx.search(msg); long unsigned int cookie=rx.cap(1).toUInt(); if(m_invitations.contains(cookie)) { MSNInvitation *msnI=m_invitations[cookie]; msnI->parseInvitation(msg); } else if( msg.contains("Invitation-Command: INVITE") ) { if( msg.contains(MSNFileTransferSocket::applicationID()) ) { MSNFileTransferSocket *MFTS=new MSNFileTransferSocket(myself()->account()->accountId(),c,true,this); connect(MFTS, TQ_SIGNAL( done(MSNInvitation*) ) , this , TQ_SLOT( invitationDone(MSNInvitation*) )); m_invitations.insert( cookie , MFTS); MFTS->parseInvitation(msg); setCanBeDeleted(false); } else { MSNInvitation *i=0l; emit invitation( i , msg, cookie, this, c ); if(i) { m_invitations.insert( cookie , i ); //don't delete this if all invitation are not done setCanBeDeleted(false); } else { rx=TQRegExp("Application-Name: ([^\\r\\n]*)"); rx.search(msg); TQString inviteName = rx.cap( 1 ); TQString body = i18n( "%1 has sent an unimplemented invitation, the invitation was rejected.\n" "The invitation was: %2" ) .arg( c->property( Kopete::Global::Properties::self()->nickName()).value().toString(), inviteName ); Kopete::Message tmpMsg = Kopete::Message( c , members() , body , Kopete::Message::Internal, Kopete::Message::PlainText); appendMessage(tmpMsg); m_chatService->sendCommand( "MSG" , "N", true, MSNInvitation::unimplemented(cookie) ); } } } } void MSNChatSession::invitationDone(MSNInvitation* MFTS) { kdDebug(14140) << k_funcinfo <cookie()); // MFTS->deleteLater(); delete MFTS; if(!m_chatService && m_invitations.isEmpty()) setCanBeDeleted(true); } void MSNChatSession::sendFile(const TQString &fileLocation, const TQString &/*fileName*/, long unsigned int fileSize) { // TODO create a switchboard to send the file is one is not available. if(m_chatService && members().getFirst()) { m_chatService->PeerDispatcher()->sendFile(fileLocation, (TQ_INT64)fileSize, members().getFirst()->contactId()); } } void MSNChatSession::initInvitation(MSNInvitation* invitation) { connect(invitation->object(), TQ_SIGNAL( done(MSNInvitation*) ) , this , TQ_SLOT( invitationDone(MSNInvitation*) )); m_invitations.insert( invitation->cookie() , invitation); if(m_chatService) { m_chatService->sendCommand( "MSG" , "N", true, invitation->invitationHead().utf8() ); invitation->setState(MSNInvitation::Invited); } else { startChatSession(); } } void MSNChatSession::slotRequestPicture() { TQPtrList mb=members(); MSNContact *c = static_cast( mb.first() ); if(!c) return; if( !c->hasProperty(Kopete::Global::Properties::self()->photo().key())) { if(m_chatService) { if( !c->object().isEmpty() ) m_chatService->requestDisplayPicture(); } else if(myself()->onlineStatus().isDefinitelyOnline() && myself()->onlineStatus().status() != Kopete::OnlineStatus::Invisible ) startChatSession(); } else { //we already have the picture, just show it. KRun::runURL( KURL::fromPathOrURL( c->property(Kopete::Global::Properties::self()->photo()).value().toString() ), "image/png" ); } } void MSNChatSession::slotDisplayPictureChanged() { TQPtrList mb=members(); MSNContact *c = static_cast( mb.first() ); if ( c && m_image ) { if(c->hasProperty(Kopete::Global::Properties::self()->photo().key())) { int sz=22; // get the size of the toolbar were the aciton is plugged. // if you know a better way to get the toolbar, let me know TDEMainWindow *w= view(false) ? dynamic_cast( view(false)->mainWidget()->topLevelWidget() ) : 0L; if(w) { //We connected that in the constructor. we don't need to keep this slot active. disconnect( Kopete::ChatSessionManager::self() , TQ_SIGNAL(viewActivated(KopeteView* )) , this, TQ_SLOT(slotDisplayPictureChanged()) ); TQPtrListIterator it=w->toolBarIterator() ; TDEAction *imgAction=actionCollection()->action("msnDisplayPicture"); if(imgAction) while(it) { TDEToolBar *tb=*it; if(imgAction->isPlugged(tb)) { sz=tb->iconSize(); //ipdate if the size of the toolbar change. disconnect(tb, TQ_SIGNAL(modechange()), this, TQ_SLOT(slotDisplayPictureChanged())); connect(tb, TQ_SIGNAL(modechange()), this, TQ_SLOT(slotDisplayPictureChanged())); break; } ++it; } } TQString imgURL=c->property(Kopete::Global::Properties::self()->photo()).value().toString(); TQImage scaledImg = TQPixmap( imgURL ).convertToImage().smoothScale( sz, sz ); if(!scaledImg.isNull()) m_image->setPixmap( scaledImg ); else { //the image has maybe not been transfered correctly.. force to download again c->removeProperty(Kopete::Global::Properties::self()->photo()); //slotDisplayPictureChanged(); //don't do that or we might end in a infinite loop } TQToolTip::add( m_image, "" ); } else { TDEConfig *config = TDEGlobal::config(); config->setGroup( "MSN" ); if ( config->readNumEntry( "DownloadPicture", 1 ) >= 1 && !c->object().isEmpty() ) slotRequestPicture(); } } } void MSNChatSession::slotDebugRawCommand() { #if !defined NDEBUG if ( !m_chatService ) return; MSNDebugRawCmdDlg *dlg = new MSNDebugRawCmdDlg( 0L ); int result = dlg->exec(); if( result == TQDialog::Accepted && m_chatService ) { m_chatService->sendCommand( dlg->command(), dlg->params(), dlg->addId(), dlg->msg().replace("\n","\r\n").utf8() ); } delete dlg; #endif } void MSNChatSession::receivedTypingMsg( const TQString &contactId, bool b ) { MSNContact *c = dynamic_cast( account()->contacts()[ contactId ] ); if(c && m_newSession && !view(false)) { //this was originaly in MSNAccount::slotCreateChat TDEGlobal::config()->setGroup( "MSN" ); bool notifyNewChat = TDEGlobal::config()->readBoolEntry( "NotifyNewChat", false ); if ( notifyNewChat ) { // this internal message should open the window if they not exist TQString body = i18n( "%1 has started a chat with you" ).arg( c->metaContact()->displayName() ); Kopete::Message tmpMsg = Kopete::Message( c, members(), body, Kopete::Message::Internal, Kopete::Message::PlainText ); appendMessage( tmpMsg ); } } m_newSession=false; if(c) Kopete::ChatSession::receivedTypingMsg(c,b); } void MSNChatSession::slotSendNudge() { if(m_chatService) { m_chatService->sendNudge(); Kopete::Message msg = Kopete::Message( myself(), members() , i18n ( "has sent a nudge" ), Kopete::Message::Outbound, Kopete::Message::PlainText, TQString(), Kopete::Message::TypeAction ); appendMessage( msg ); } } void MSNChatSession::slotNudgeReceived(const TQString& handle) { Kopete::Contact *c = account()->contacts()[ handle ] ; if(!c) c=members().getFirst(); Kopete::Message msg = Kopete::Message(c, myself(), i18n ( "has sent you a nudge" ), Kopete::Message::Inbound, Kopete::Message::PlainText, TQString(), Kopete::Message::TypeAction ); appendMessage( msg ); // Emit the nudge/buzz notification (configured by user). emitNudgeNotification(); } void MSNChatSession::slotWebcamReceive() { #if MSN_WEBCAM if(m_chatService && members().getFirst()) { m_chatService->PeerDispatcher()->startWebcam(myself()->contactId() , members().getFirst()->contactId() , true); } #endif } void MSNChatSession::slotWebcamSend() { #if MSN_WEBCAM kdDebug(14140) << k_funcinfo << endl; if(m_chatService && members().getFirst()) { m_chatService->PeerDispatcher()->startWebcam(myself()->contactId() , members().getFirst()->contactId() , false); } #endif } void MSNChatSession::slotSendFile() { TQPtrListcontacts = members(); static_cast(contacts.first())->sendFile(); } void MSNChatSession::startChatSession() { TQPtrList mb=members(); static_cast( account() )->slotStartChatSession( mb.first()->contactId() ); if(!m_timeoutTimer) { m_timeoutTimer=new TQTimer(this); connect( m_timeoutTimer , TQ_SIGNAL(timeout()), this , TQ_SLOT(slotConnectionTimeout() ) ); } m_timeoutTimer->start(20000, true); } void MSNChatSession::cleanMessageQueue( const TQString & reason ) { delete m_timeoutTimer; m_timeoutTimer=0L; uint nb=m_messagesQueue.count()+m_messagesSent.count(); if(nb==0) return; else if(nb==1) { Kopete::Message m; if(m_messagesQueue.count() == 1) m=m_messagesQueue.first(); else m=m_messagesSent.begin().data(); TQString body=i18n("The following message has not been sent correctly (%1): \n%2").arg(reason, m.plainBody()); Kopete::Message msg = Kopete::Message(m.to().first() , members() , body , Kopete::Message::Internal, Kopete::Message::PlainText); appendMessage(msg); } else { Kopete::Message m; TQString body=i18n("These messages have not been sent correctly (%1):
    ").arg(reason); for ( TQMap::iterator it = m_messagesSent.begin(); it!=m_messagesSent.end(); it = m_messagesSent.begin() ) { m=it.data(); body+= "
  • "+m.escapedBody()+"
  • "; m_messagesSent.remove(it); } for ( TQValueList::iterator it = m_messagesQueue.begin(); it!=m_messagesQueue.end(); it = m_messagesQueue.begin() ) { m=(*it); body+= "
  • "+m.escapedBody()+"
  • "; m_messagesQueue.remove(it); } body+="
"; Kopete::Message msg = Kopete::Message(m.to().first() , members() , body , Kopete::Message::Internal, Kopete::Message::RichText); appendMessage(msg); } m_messagesQueue.clear(); m_messagesSent.clear(); messageSucceeded(); //stop stupid animation } void MSNChatSession::slotConnectionTimeout() { m_connectionTry++; if(m_chatService) { disconnect(m_chatService , 0 , this , 0 ); m_chatService->deleteLater(); m_chatService=0L; } if( m_connectionTry > 3 ) { cleanMessageQueue( i18n("Impossible to establish the connection") ); delete m_timeoutTimer; m_timeoutTimer=0L; return; } startChatSession(); } #include "msnchatsession.moc"