/* msncontact.cpp - MSN Contact Copyright (c) 2002 by Duncan Mac-Vicar Prett Copyright (c) 2002 by Ryan Cumming Copyright (c) 2002-2003 by Martijn Klingens Copyright (c) 2002-2005 by Olivier Goffart Copyright (c) 2005 by Michaƫl Larouche Kopete (c) 2002-2005 by the Kopete developers ************************************************************************* * * * 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 "msncontact.h" #include #undef KDE_NO_COMPAT #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kopetecontactlist.h" #include "kopetechatsessionmanager.h" #include "kopetemetacontact.h" #include "kopetegroup.h" #include "kopeteuiglobal.h" #include "kopeteglobal.h" #include "msninfo.h" #include "msnchatsession.h" #include "msnnotifysocket.h" #include "msnaccount.h" MSNContact::MSNContact( Kopete::Account *account, const TQString &id, Kopete::MetaContact *tqparent ) : Kopete::Contact( account, id, tqparent ) { m_deleted = false; m_allowed = false; m_blocked = false; m_reversed = false; m_moving = false; m_clientFlags=0; setFileCapable( true ); // When we are not connected, it's because we are loading the contactlist. // so we set the initial status to offline. // We set offline directly because modifying the status after is too slow. // (notification, contactlist updating,....) // // FIXME: Hacks like these shouldn't happen in the protocols, but should be // covered properly at the libkopete level instead - Martijn // // When we are connected, it can be because the user added a contact with the // wizard, and it can be because we are creating a temporary contact. // if it's added by the wizard, the status will be set immediately after. // if it's a temporary contact, better to set the unknown status. setOnlineStatus( ( tqparent && tqparent->isTemporary() ) ? MSNProtocol::protocol()->UNK : MSNProtocol::protocol()->FLN ); actionBlock = 0L; setProperty(MSNProtocol::protocol()->propEmail, id); } MSNContact::~MSNContact() { kdDebug(14140) << k_funcinfo << endl; } bool MSNContact::isReachable() { if ( account()->isConnected() && isOnline() && account()->myself()->onlinetqStatus() != MSNProtocol::protocol()->HDN ) return true; MSNChatSession *kmm=dynamic_cast(manager(Kopete::Contact::CannotCreate)); if( kmm && kmm->service() ) //the chat socket is open. than mean message will be sent return true; // When we are invisible we can't start a chat with others, make isReachable return false // (This is an MSN limitation, not a problem in Kopete) if ( !account()->isConnected() || account()->myself()->onlinetqStatus() == MSNProtocol::protocol()->HDN ) return false; //if the contact is offline, it is impossible to send it a message. but it is impossible //to be sure the contact is realy offline. For example, if the contact is not on the contactlist for //some reason. if( onlinetqStatus() == MSNProtocol::protocol()->FLN && ( isAllowed() || isBlocked() ) && !serverGroups().isEmpty() ) return false; return true; } Kopete::ChatSession *MSNContact::manager( Kopete::Contact::CanCreateFlags canCreate ) { Kopete::ContactPtrList chatmembers; chatmembers.append(this); Kopete::ChatSession *_manager = Kopete::ChatSessionManager::self()->findChatSession( account()->myself(), chatmembers, protocol() ); MSNChatSession *manager = dynamic_cast( _manager ); if(!manager && canCreate==Kopete::Contact::CanCreate) { manager = new MSNChatSession( protocol(), account()->myself(), chatmembers ); static_cast( account() )->slotStartChatSession( contactId() ); } return manager; } TQPtrList *MSNContact::customContextMenuActions() { TQPtrList *m_actionCollection = new TQPtrList; // Block/unblock Contact TQString label = isBlocked() ? i18n( "Unblock User" ) : i18n( "Block User" ); if( !actionBlock ) { actionBlock = new KAction( label, "msn_blocked",0, this, TQT_SLOT( slotBlockUser() ), this, "actionBlock" ); //show profile actionShowProfile = new KAction( i18n("Show Profile") , 0, this, TQT_SLOT( slotShowProfile() ), this, "actionShowProfile" ); // Send mail (only available if it is an hotmail account) actionSendMail = new KAction( i18n("Send Email...") , "mail_generic",0, this, TQT_SLOT( slotSendMail() ), this, "actionSendMail" ); // Invite to receive webcam actionWebcamReceive = new KAction( i18n( "View Contact's Webcam" ), "webcamreceive", 0, this, TQT_SLOT(slotWebcamReceive() ), this, "msnWebcamReceive" ) ; //Send webcam action actionWebcamSend = new KAction( i18n( "Send Webcam" ), "webcamsend", 0, this, TQT_SLOT(slotWebcamSend() ), this, "msnWebcamSend" ) ; } else actionBlock->setText( label ); actionSendMail->setEnabled( static_cast(account())->isHotmail()); m_actionCollection->append( actionBlock ); m_actionCollection->append( actionShowProfile ); m_actionCollection->append( actionSendMail ); m_actionCollection->append( actionWebcamReceive ); m_actionCollection->append( actionWebcamSend ); return m_actionCollection; } void MSNContact::slotBlockUser() { MSNNotifySocket *notify = static_cast( account() )->notifySocket(); if( !notify ) { KMessageBox::error( Kopete::UI::Global::mainWidget(), i18n( "Please go online to block or unblock a contact." ), i18n( "MSN Plugin" )); return; } if( m_blocked ) { notify->removeContact( contactId(), MSNProtocol::BL, TQString(), TQString() ); } else { if(m_allowed) notify->removeContact( contactId(), MSNProtocol::AL, TQString(), TQString() ); else notify->addContact( contactId(), MSNProtocol::BL, TQString(), TQString(), TQString() ); } } void MSNContact::slotUserInfo() { KDialogBase *infoDialog=new KDialogBase( 0l, "infoDialog", /*modal = */false, TQString(), KDialogBase::Close , KDialogBase::Close, false ); TQString nick=property( Kopete::Global::Properties::self()->nickName()).value().toString(); TQString personalMessage=property( MSNProtocol::protocol()->propPersonalMessage).value().toString(); MSNInfo *info=new MSNInfo ( infoDialog,"info"); info->m_id->setText( contactId() ); info->m_displayName->setText(nick); info->m_personalMessage->setText(personalMessage); info->m_phh->setText(m_phoneHome); info->m_phw->setText(m_phoneWork); info->m_phm->setText(m_phoneMobile); info->m_reversed->setChecked(m_reversed); connect( info->m_reversed, TQT_SIGNAL(toggled(bool)) , this, TQT_SLOT(slotUserInfoDialogReversedToggled())); infoDialog->setMainWidget(info); infoDialog->setCaption(nick); infoDialog->show(); } void MSNContact::slotUserInfoDialogReversedToggled() { //workaround to make this checkboxe readonly const TQCheckBox *cb=dynamic_cast(sender()); if(cb && cb->isChecked()!=m_reversed) const_cast(cb)->setChecked(m_reversed); } void MSNContact::deleteContact() { kdDebug( 14140 ) << k_funcinfo << endl; MSNNotifySocket *notify = static_cast( account() )->notifySocket(); if( notify ) { if( hasProperty(MSNProtocol::protocol()->propGuid.key()) ) { // Remove from all groups he belongs (if applicable) for( TQMap::Iterator it = m_serverGroups.begin(); it != m_serverGroups.end(); ++it ) { kdDebug(14140) << k_funcinfo << "Removing contact from group \"" << it.key() << "\"" << endl; notify->removeContact( contactId(), MSNProtocol::FL, guid(), it.key() ); } // Then trully remove it from server contact list, // because only removing the contact from his groups isn't sufficent from MSNP11. kdDebug( 14140 ) << k_funcinfo << "Removing contact from top-level." << endl; notify->removeContact( contactId(), MSNProtocol::FL, guid(), TQString()); } else { kdDebug( 14140 ) << k_funcinfo << "The contact is already removed from server, just delete it" << endl; deleteLater(); } } else { // FIXME: This case should be handled by Kopete, not by the plugins :( - Martijn // FIXME: We should be able to delete contacts offline, and remove it from server next time we go online - Olivier KMessageBox::error( Kopete::UI::Global::mainWidget(), i18n( "Please go online to remove a contact from your contact list." ), i18n( "MSN Plugin" )); } } bool MSNContact::isBlocked() const { return m_blocked; } void MSNContact::setBlocked( bool blocked ) { if( m_blocked != blocked ) { m_blocked = blocked; //update the status setOnlineStatus(m_currenttqStatus); //m_currenttqStatus is used here. previously it was onlinetqStatus() but this may cause problem when // the account is offline because of the Kopete::Contact::OnlineStatus() account offline hack. } } bool MSNContact::isAllowed() const { return m_allowed; } void MSNContact::setAllowed( bool allowed ) { m_allowed = allowed; } bool MSNContact::isReversed() const { return m_reversed; } void MSNContact::setReversed( bool reversed ) { m_reversed= reversed; } bool MSNContact::isDeleted() const { return m_deleted; } void MSNContact::setDeleted( bool deleted ) { m_deleted= deleted; } uint MSNContact::clientFlags() const { return m_clientFlags; } void MSNContact::setClientFlags( uint flags ) { if(m_clientFlags != flags) { if(hasProperty( MSNProtocol::protocol()->propClient.key() )) { if( flags & MSNProtocol::WebMessenger) setProperty( MSNProtocol::protocol()->propClient , i18n("Web Messenger") ); else if( flags & MSNProtocol::WindowsMobile) setProperty( MSNProtocol::protocol()->propClient , i18n("Windows Mobile") ); else if( flags & MSNProtocol::MSNMobileDevice) setProperty( MSNProtocol::protocol()->propClient , i18n("MSN Mobile") ); else if( m_obj.contains("kopete") ) setProperty( MSNProtocol::protocol()->propClient , i18n("Kopete") ); } } m_clientFlags=flags; } void MSNContact::setInfo(const TQString &type,const TQString &data ) { if( type == "PHH" ) { m_phoneHome = data; setProperty(MSNProtocol::protocol()->propPhoneHome, data); } else if( type == "PHW" ) { m_phoneWork=data; setProperty(MSNProtocol::protocol()->propPhoneWork, data); } else if( type == "PHM" ) { m_phoneMobile = data; setProperty(MSNProtocol::protocol()->propPhoneMobile, data); } else if( type == "MOB" ) { if( data == "Y" ) m_phone_mob = true; else if( data == "N" ) m_phone_mob = false; else kdDebug( 14140 ) << k_funcinfo << "Unknown MOB " << data << endl; } else if( type == "MFN" ) { setProperty(Kopete::Global::Properties::self()->nickName(), data ); } else { kdDebug( 14140 ) << k_funcinfo << "Unknow info " << type << " " << data << endl; } } void MSNContact::serialize( TQMap &serializedData, TQMap & /* addressBookData */ ) { // Contact id and display name are already set for us, only add the rest TQString groups; bool firstEntry = true; for( TQMap::ConstIterator it = m_serverGroups.begin(); it != m_serverGroups.end(); ++it ) { if( !firstEntry ) { groups += ","; firstEntry = true; } groups += it.key(); } TQString lists="C"; if(m_blocked) lists +="B"; if(m_allowed) lists +="A"; if(m_reversed) lists +="R"; serializedData[ "groups" ] = groups; serializedData[ "PHH" ] = m_phoneHome; serializedData[ "PHW" ] = m_phoneWork; serializedData[ "PHM" ] = m_phoneMobile; serializedData[ "lists" ] = lists; serializedData[ "obj" ] = m_obj; serializedData[ "contactGuid" ] = guid(); } TQString MSNContact::guid(){ return property(MSNProtocol::protocol()->propGuid).value().toString(); } TQString MSNContact::phoneHome(){ return m_phoneHome ;} TQString MSNContact::phoneWork(){ return m_phoneWork ;} TQString MSNContact::phoneMobile(){ return m_phoneMobile ;} const TQMap MSNContact::serverGroups() const { return m_serverGroups; } void MSNContact::clearServerGroups() { m_serverGroups.clear(); } void MSNContact::sync( unsigned int changed ) { if( ! (changed & Kopete::Contact::MovedBetweenGroup) ) return; //we are only interested by a change in groups if(!metaContact() || metaContact()->isTemporary() ) return; if(m_moving) { //We need to make sure that syncGroups is not called twice successively // because m_serverGroups will be only updated with the reply of the server // and then, the same command can be sent twice. // FIXME: if this method is called a seconds times, that mean change can be // done in the contactlist. we should found a way to recall this // method later. (a TQTimer?) kdDebug( 14140 ) << k_funcinfo << " This contact is already moving. Abort sync id: " << contactId() << endl; return; } MSNNotifySocket *notify = static_cast( account() )->notifySocket(); if( !notify ) { //We are not connected, we will doing it next connection. //Force to reload the whole contactlist from server to suync groups when connecting account()->configGroup()->writeEntry("serial", 0 ); return; } if(m_deleted) //the contact hasn't been synced from server yet. return; unsigned int count=m_serverGroups.count(); //Don't add the contact if it's myself. if(count==0 && contactId() == account()->accountId()) return; //STEP ONE : add the contact to every kopetegroups where the MC is TQPtrList groupList = metaContact()->groups(); for ( Kopete::Group *group = groupList.first(); group; group = groupList.next() ) { //For each group, ensure it is on the MSN server if( !group->pluginData( protocol() , account()->accountId() + " id" ).isEmpty() ) { TQString Gid=group->pluginData( protocol(), account()->accountId() + " id" ); if( !static_cast( account() )->m_groupList.contains(Gid) ) { // ohoh! something is corrupted on the contactlist.xml // anyway, we never should add a contact to an unexisting group on the server. // This shouln't be possible anymore 2004-06-10 -Olivier //repair the problem group->setPluginData( protocol() , account()->accountId() + " id" , TQString()); group->setPluginData( protocol() , account()->accountId() + " displayName" , TQString()); kdWarning( 14140 ) << k_funcinfo << " Group " << group->displayName() << " marked with id #" <displayName().isEmpty() && group->type() == Kopete::Group::Normal) //not the top-level { //Create the group and add the contact static_cast( account() )->addGroup( group->displayName(),contactId() ); count++; m_moving=true; } } else if( !m_serverGroups.contains(Gid) ) { //Add the contact to the group on the server notify->addContact( contactId(), MSNProtocol::FL, TQString(), guid(), Gid ); count++; m_moving=true; } } else { if(!group->displayName().isEmpty() && group->type() == Kopete::Group::Normal) //not the top-level { //Create the group and add the contact static_cast( account() )->addGroup( group->displayName(),contactId() ); //WARNING: if contact is not correctly added (because the group was not aded corrdctly for hinstance), // if we increment the count, the contact can be deleted from the old group, and be lost :-( count++; m_moving=true; } } } //STEP TWO : remove the contact from groups where the MC is not, but let it at least in one group //contact is not in that group. on the server. we will remove them dirrectly after the loop TQValueList removinglist; for( TQMap::Iterator it = m_serverGroups.begin();(count > 1 && it != m_serverGroups.end()); ++it ) { if( !static_cast( account() )->m_groupList.contains(it.key()) ) { // ohoh! something is corrupted on the contactlist.xml // anyway, we never should add a contact to an unexisting group on the server. //repair the problem ... //contactRemovedFromGroup( it.key() ); // ... later (we can't remove it from the map now ) removinglist.append(it.key()); count--; kdDebug( 14140 ) << k_funcinfo << "the group marked with id #" << it.key() << " does not seems to be anymore on the server" << endl; continue; } Kopete::Group *group=it.data(); if(!group) //we can't trust the data of it() see in MSNProtocol::deserializeContact why group=static_cast( account() )->m_groupList[it.key()]; if( !metaContact()->groups().contains(group) ) { m_moving=true; notify->removeContact( contactId(), MSNProtocol::FL, guid(), it.key() ); count--; } } for(TQValueList::Iterator it= removinglist.begin() ; it != removinglist.end() ; ++it ) contactRemovedFromGroup(*it); //FINAL TEST: is the contact at least in a group.. // this may happens if we just added a temporary contact to top-level // we add the contact to the group #0 (the default one) /*if(count==0) { // notify->addContact( contactId(), MSNProtocol::FL, TQString(), guid(), "0"); }*/ } void MSNContact::contactAddedToGroup( const TQString& groupId, Kopete::Group *group ) { m_serverGroups.insert( groupId, group ); m_moving=false; } void MSNContact::contactRemovedFromGroup( const TQString& groupId ) { m_serverGroups.remove( groupId ); if(m_serverGroups.isEmpty() && !m_moving) { deleteLater(); } m_moving=false; } void MSNContact::rename( const TQString &newName ) { //kdDebug( 14140 ) << k_funcinfo << "From: " << displayName() << ", to: " << newName << endl; /* if( newName == displayName() ) return;*/ // FIXME: This should be called anymore. MSNNotifySocket *notify = static_cast( account() )->notifySocket(); if( notify ) { notify->changePublicName( newName, contactId() ); } } void MSNContact::slotShowProfile() { KRun::runURL( KURL( TQString::tqfromLatin1("http://members.msn.com/?pgmarket=it-it&mem=") + contactId()) , "text/html" ); } /** * FIXME: Make this a standard KMM API call */ void MSNContact::sendFile( const KURL &sourceURL, const TQString &altFileName, uint /*fileSize*/ ) { TQString filePath; //If the file location is null, then get it from a file open dialog if( !sourceURL.isValid() ) filePath = KFileDialog::getOpenFileName( TQString() ,"*", 0l , i18n( "Kopete File Transfer" )); else filePath = sourceURL.path(-1); //kdDebug(14140) << "MSNContact::sendFile: File chosen to send:" << fileName << endl; if ( !filePath.isEmpty() ) { TQ_UINT32 fileSize = TQFileInfo(filePath).size(); //Send the file static_cast( manager(Kopete::Contact::CanCreate) )->sendFile( filePath, altFileName, fileSize ); } } void MSNContact::setOnlineStatus(const Kopete::OnlineStatus& status) { if(isBlocked() && status.internalStatus() < 15) { Kopete::Contact::setOnlineStatus( Kopete::OnlineStatus(status.status() , (status.weight()==0) ? 0 : (status.weight() -1) , protocol() , status.internalStatus()+15 , status.overlayIcons() + TQStringList("msn_blocked") , i18n("%1|Blocked").tqarg( status.description() ) ) ); } else if(!isBlocked() && status.internalStatus() >= 15) { //the user is not blocked, but the status is blocked switch(status.internalStatus()-15) { case 1: Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->NLN); break; case 2: Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->BSY); break; case 3: Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->BRB); break; case 4: Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->AWY); break; case 5: Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->PHN); break; case 6: Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->LUN); break; case 7: Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->FLN); break; case 8: Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->HDN); break; case 9: Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->IDL); break; default: Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->UNK); break; } } else Kopete::Contact::setOnlineStatus(status); m_currenttqStatus=status; } void MSNContact::slotSendMail() { MSNNotifySocket *notify = static_cast( account() )->notifySocket(); if( notify ) { notify->sendMail( contactId() ); } } void MSNContact::setDisplayPicture(KTempFile *f) { //copy the temp file somewere else. // in a better world, the file could be dirrectly wrote at the correct location. // but the custom emoticon code is to deeply merged in the display picture code while it could be separated. TQString newlocation=locateLocal( "appdata", "msnpictures/"+ contactId().lower().replace(TQRegExp("[./~]"),"-") +".png" ) ; KIO::Job *j=KIO::file_move( KURL::fromPathOrURL( f->name() ) , KURL::fromPathOrURL( newlocation ) , -1, true /*overwrite*/ , false /*resume*/ , false /*showProgressInfo*/ ); f->setAutoDelete(false); delete f; //let the time to KIO to copy the file connect(j, TQT_SIGNAL(result(KIO::Job *)) , this, TQT_SLOT(slotEmitDisplayPictureChanged() )); } void MSNContact::slotEmitDisplayPictureChanged() { TQString newlocation=locateLocal( "appdata", "msnpictures/"+ contactId().lower().replace(TQRegExp("[./~]"),"-") +".png" ) ; setProperty( Kopete::Global::Properties::self()->photo() , newlocation ); emit displayPictureChanged(); } void MSNContact::setObject(const TQString &obj) { if(m_obj==obj && (obj.isEmpty() || hasProperty(Kopete::Global::Properties::self()->photo().key()))) return; m_obj=obj; removeProperty( Kopete::Global::Properties::self()->photo() ) ; emit displayPictureChanged(); KConfig *config = KGlobal::config(); config->setGroup( "MSN" ); if ( config->readNumEntry( "DownloadPicture", 2 ) >= 2 && !obj.isEmpty() && account()->myself()->onlinetqStatus().status() != Kopete::OnlineStatus::Invisible ) manager(Kopete::Contact::CanCreate); //create the manager which will download the photo automatically. } #include "msncontact.moc" // vim: set noet ts=4 sts=4 sw=4: