/*************************************************************************** contactbase.cpp - description ------------------- begin : Thu Jan 16 2003 copyright : (C) 2003 by Mike K. Bennett email : mkb137b@hotmail.com ***************************************************************************/ /*************************************************************************** * * * 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 "contactbase.h" #include "../contact/msnobject.h" // only used for detectClientAppName()! #include "../network/applications/applicationlist.h" #include "../utils/kmessshared.h" #include "../kmessdebug.h" #include <QFile> #include <QImage> #include <KLocale> #include <KStandardDirs> // The constructor ContactBase::ContactBase(QString handle, QString friendlyName, uint capabilities) : capabilities_(capabilities) , handle_(handle) , applicationList_(0) { // Use the handle in the rare cases where a friendly name is not available if( friendlyName.isEmpty() ) { friendlyName_ = handle; } else { friendlyName_ = friendlyName; } } // The destructor ContactBase::~ContactBase() { delete applicationList_; applicationList_ = 0; } // Add a custom emoticon definition void ContactBase::addEmoticonDefinition(const QString &emoticonCode, const QString &msnObjectDataHash ) { #ifdef KMESSDEBUG_CONTACTBASE kDebug() << "adding '" << emoticonCode << "' to pending replacements"; #endif // Escape the code so the HTML parser won't be fooled. QString shortcut( emoticonCode ); KMessShared::htmlEscape( shortcut ); // Add emoticon to pending list, until Msn object is received. pendingEmoticons_.insert( msnObjectDataHash, shortcut ); // Escape the code to insert it safely into a regular expression QString escapedCode( QRegExp::escape( shortcut ) ); // Update regexp pattern if( pendingRegExp_.isEmpty() ) { pendingRegExp_ = QRegExp( escapedCode ); } else { pendingRegExp_ = QRegExp( pendingRegExp_.pattern() + "|" + escapedCode ); } #ifdef KMESSTEST KMESS_ASSERT( pendingRegExp_.isValid() ); #endif } // Add a custom emoticon file void ContactBase::addEmoticonFile(const QString &msnObjectDataHash, const QString &filename) { // Check whether the mapping is available. if( ! pendingEmoticons_.contains(msnObjectDataHash) ) { kWarning() << "Received an custom emoticon, but no emoticon code found!"; return; } // Get the emoticon code from pending list, remove it QString emoticonCode( pendingEmoticons_[msnObjectDataHash] ); // no QString& because record gets removed. pendingEmoticons_.remove(msnObjectDataHash); #ifdef KMESSDEBUG_CONTACTBASE kDebug() << "linking '" << emoticonCode << "' to received file."; #endif // Check if filename exists, attempt to read it. if( ! QFile::exists(filename) ) { kWarning() << "Could not read emoticon file: file not found!"; return; } QImage emoticonData(filename); if( emoticonData.isNull() ) { kWarning() << "Could not read emoticon file!"; return; } #ifdef KMESSDEBUG_CONTACTBASE kDebug() << "Setting image for '" << emoticonCode << " to " << filename; #endif // Escape the code to insert it safely into a regular expression QString escapedCode( QRegExp::escape( emoticonCode ) ); // Update regexp pattern if( emoticonRegExp_.isEmpty() ) { emoticonRegExp_ = QRegExp( escapedCode ); } else { emoticonRegExp_ = QRegExp( emoticonRegExp_.pattern() + "|" + escapedCode ); } // Update pending regexp pattern. pendingRegExp_ = QRegExp(pendingRegExp_.pattern().remove( QRegExp("(^|\\|)" + escapedCode) )); #ifdef KMESSTEST KMESS_ASSERT( emoticonRegExp_.isValid() ); KMESS_ASSERT( pendingRegExp_.isValid() ); #endif // Get the image dimensions from the file int originalWidth = emoticonData.width(); int originalHeight = emoticonData.height(); // Resize the displayed image to match KMess' maximum emoticon size int width = originalWidth; // qMin( EMOTICONS_MAX_SIZE, originalWidth ); // Require only a max height int height = qMin( EMOTICONS_MAX_SIZE, originalHeight ); if( originalWidth > originalHeight ) { height = ( width * originalHeight ) / originalWidth; } else { width = ( height * originalWidth ) / originalHeight; } // Add replacement. QString altCode ( emoticonCode ); altCode = KMessShared::htmlUnescape( altCode ).replace("'", "'"); QString replacement( "<img src='" + filename + "' alt='" + altCode + "' width='" + QString::number( width ) + "' height='" + QString::number( height ) + "' valign='middle'" + " class='customEmoticon' />" ); customEmoticons_.insert(emoticonCode, replacement); } // Register the contact as participant in the chat session void ContactBase::addSwitchboardConnection( const MsnSwitchboardConnection *connection ) { #ifdef KMESSDEBUG_CONTACTBASE kDebug() << "adding connection to list."; #endif if( chatSessions_.contains(connection) ) { kWarning() << "chat session is already added."; return; } chatSessions_.append( connection ); } // Initialize an application list for the contact. ApplicationList * ContactBase::createApplicationList() { if( applicationList_ != 0 ) { kWarning() << "application list already created for contact '" << handle_ << "'."; } else { applicationList_ = new ApplicationList( handle_ ); connect( applicationList_, SIGNAL( applicationsAborted(const QString &) ), this, SLOT ( slotApplicationListAborted() ) ); } return applicationList_; } // Set the application name based on the capabilities void ContactBase::detectClientAppName( uint capabilities, const MsnObject *msnObject ) { // Note there are two ways to detect the client name. // - in the notification connection, we can read the 'capabilities' and filename from the 'msnobject' (which some thirdparty clients tag with their name). // - in the switchboard connection, a x-clientcaps message is sent by most thirdparty clients with exact version information. // Use capabilities to give an indication what client we're dealing with. uint clientVersion = capabilities & 0xF0000000; // Find msn version float msnVersion = 0; QString suffix; switch( clientVersion ) { case 0: break; case MSN_CAP_MSN60: msnVersion = 6.0f; break; case MSN_CAP_MSN61: msnVersion = 6.1f; break; case MSN_CAP_MSN62: msnVersion = 6.2f; break; case MSN_CAP_MSN70: msnVersion = 7.0f; break; // default caps 0x4000C024: ink-gif, multi-packet, direct-im, winks case MSN_CAP_MSN75: msnVersion = 7.5f; break; case MSN_CAP_MSN80: msnVersion = 8.0f; break; case MSN_CAP_MSN81: msnVersion = 8.1f; break; // default caps 0x765DC02C: ink-gif, (ink-isf), multi-packet, direct-im, winks, shared-search, voice-clips, secure-channel, sip, folder-sharing, turn, uun case MSN_CAP_MSN85: msnVersion = 8.5f; break; // default caps 0x865DC02C: ink-gif, ink-isf, multi-packet, direct-im, winks, shared-search, voice-clips, secure-channel, sip, folder-sharing case MSN_CAP_MSN90beta: msnVersion = 9.0f; suffix = " beta"; break; // default caps 0x963CC03C: case MSN_CAP_MSN2009: msnVersion = 2009.0f; break; default: kWarning() << "Unable to parse the WLM version of for the contact" << handle_ << ", msncversion=" << ( clientVersion >> 28 ) << " capabilities=" << capabilities; msnVersion = 9999999; } // Third party clients also include their name in the MsnObject, // since that's already available at the notification connection. QString pictureClientName; if( msnObject != 0 ) { const QString &pictureLocation = msnObject->getLocation(); if( ! pictureLocation.isEmpty() && pictureLocation != "0" // WLM 8.1 && ! pictureLocation.startsWith("TFR") ) // MSN / WLM { // Known variations: if( pictureLocation == "KMess.tmp" ) { pictureClientName = "KMess"; } else if( pictureLocation == "Mercury.tmp" ) { pictureClientName = "Mercury"; } else if( pictureLocation == "kopete.tmp" ) { pictureClientName = "Kopete"; // no longer works since KDE 4.2, Kopete uses libmsn now. } else if( pictureLocation == "amsn.tmp" ) { pictureClientName = "aMSN"; } else if( pictureLocation == "EncartaIcon.png" ) { // Encarta Instant Answers. // ignore, no special string for this one. } else { #ifdef KMESSDEBUG_CONTACTBASE kDebug() << "NOTICE: Unknown client name in MsnObject location:" << pictureLocation; #endif } } } if( ! pictureClientName.isNull() ) { clientAppName_ = pictureClientName; } // Check capabilities for special software else if( capabilities & MSN_CAP_WIN_MOBILE ) { clientAppName_ = i18n("Windows Mobile"); } else if( capabilities & MSN_CAP_WEB_CLIENT ) { clientAppName_ = i18n("Web Messenger"); } else if( capabilities & MSN_CAP_MSO_CLIENT ) { clientAppName_ = i18n("Office Communicator"); } else if( capabilities & MSN_CAP_BOT ) { clientAppName_ = i18n("Messenger Bot"); } else if( capabilities & MSN_CAP_MCE_CLIENT ) { clientAppName_ = i18n("Windows Media Center"); } else { if( msnVersion == 0 ) { // pre p2p clients. msn 4.x is locked out after the switch to SSL logins. clientAppName_ = i18n("MSN Messenger %1 compatible", QString("5.0")); } else if( msnVersion < 8.0 ) { // pre wlm client clientAppName_ = i18n("MSN Messenger %1 compatible", QString::number( msnVersion ) ); } else { // WLM client. // See if it has all capabilities of WLM 8.1 / 2009, or it's a third party client which is just faking... // ink-isf is optional, requires Windows Journal to be installed. const uint wlm80Mask = MSN_CAP_INK_GIF | MSN_CAP_MULTI_PACKET | MSN_CAP_DIRECT_IM | MSN_CAP_WINKS | MSN_CAP_SHARED_SEARCH | MSN_CAP_VOICE_CLIPS | MSN_CAP_SCHANNEL | MSN_CAP_SIP_INVITE | MSN_CAP_FOLDER_SHARING; // FIXME: turn / uun seen in 81, also in 8.0? const uint wlm2009Mask = MSN_CAP_INK_GIF | MSN_CAP_MULTI_PACKET | MSN_CAP_DIRECT_IM | MSN_CAP_WINKS | MSN_CAP_VOICE_CLIPS | MSN_CAP_SCHANNEL | MSN_CAP_SIP_INVITE | MSN_CAP_UNKN_2009 | MSN_CAP_P2P_TURN | MSN_CAP_P2P_UUN; bool isRealWlm = ( ( msnVersion < 9.0 && ( capabilities & wlm80Mask ) == wlm80Mask ) || ( capabilities & wlm2009Mask ) == wlm2009Mask ); if( isRealWlm ) { if( msnVersion < 9999999 ) { // Supports all default caps of 8.1 or 2009, remove the "compatible" string. This truely is WLM. clientAppName_ = i18n("Windows Live Messenger %1", QString::number( msnVersion ) + suffix ); } else { // Beyond our current finger print database, can't show version number yet. clientAppName_ = i18n("Windows Live Messenger"); } } else { if( msnVersion < 9999999 ) { clientAppName_ = i18n("Windows Live Messenger %1 compatible", QString::number( msnVersion ) ); } else { // Kopete seams to use 0xc0000000 as capabilities.. 2 versions beyond WLM2009 // It's caps are 0xc0148028; ink-isf, multi-packet, winks, voice-clips, sip clientAppName_ = i18n("Windows Live Messenger compatible", QString::number( msnVersion ) ); } } } } #ifdef KMESSDEBUG_CONTACTBASE if( isOnline() ) { QStringList caps; if( capabilities & MSN_CAP_WIN_MOBILE ) caps << "mobile-client"; // 0x1 if( capabilities & MSN_CAP_MSN8_USER ) caps << "msn8-user?"; // 0x2 if( capabilities & MSN_CAP_MSO_CLIENT ) caps << "office-client"; // 0x800 if( capabilities & MSN_CAP_WEB_CLIENT ) caps << "web-client"; // 0x200 if( capabilities & MSN_CAP_BOT ) caps << "bot-client"; // 0x20000 if( capabilities & MSN_CAP_MCE_CLIENT ) caps << "mce-client"; // 0x2000 if( capabilities & MSN_CAP_INK_GIF ) caps << "ink-gif"; // 0x4 if( capabilities & MSN_CAP_INK_ISF ) caps << "ink-isf"; // 0x8 if( capabilities & MSN_CAP_MULTI_PACKET ) caps << "multi-packet"; // 0x20 if( capabilities & MSN_CAP_DIRECT_IM ) caps << "direct-im"; // 0x4000 if( capabilities & MSN_CAP_WINKS ) caps << "winks"; // 0x8000 if( capabilities & MSN_CAP_SHARED_SEARCH ) caps << "shared-search"; // 0x10000 if( capabilities & MSN_CAP_VOICE_CLIPS ) caps << "voice-clips"; // 0x40000 if( capabilities & MSN_CAP_SCHANNEL ) caps << "secure-channel"; // 0x80000 if( capabilities & MSN_CAP_SIP_INVITE ) caps << "sip"; // 0x100000 if( capabilities & MSN_CAP_UNKN_2009 ) caps << "unknown-2009"; // 0x200000 if( capabilities & MSN_CAP_FOLDER_SHARING ) caps << "folder-sharing"; // 0x400000 if( capabilities & MSN_CAP_P2P_TURN ) caps << "p2p-turn"; // 0x2000000 if( capabilities & MSN_CAP_P2P_UUN ) caps << "p2p-uun"; // 0x4000000 if( capabilities & MSN_CAP_VIDEO_CHAT ) caps << "has-webcam"; // 0x10 if( capabilities & MSN_CAP_MSN_MOBILE ) caps << "has-mobile"; // 0x40 if( capabilities & MSN_CAP_MSN_DIRECT ) caps << "has-direct"; // 0x80 if( capabilities & MSN_CAP_LIVE_SPACE ) caps << "has-space"; // 0x1000 if( capabilities & MSN_CAP_ONECARE ) caps << "has-onecare"; // 0x1000000 kDebug() << "contact" << getHandle() << "uses client:" << clientAppName_ << "msn version:" << msnVersion << "capabilities:" << capabilities << caps; } #endif } // Return the application list of the contact ApplicationList * ContactBase::getApplicationList() const { return applicationList_; } // The capabilities of the client uint ContactBase::getCapabilities() const { return capabilities_; } // Return a user readable client name string. const QString & ContactBase::getClientAppName() const { return clientAppName_; } // Return the third party client name field const QString & ContactBase::getClientName() const { return clientName_; } // Return the default contact picture path QString ContactBase::getContactDefaultPicturePath() const { KStandardDirs *dirs = KGlobal::dirs(); return dirs->findResource( "data", "kmess/pics/unknown.png" ); } // Return the list of custom emoticons to ignore. const QStringList &ContactBase::getEmoticonBlackList() const { return emoticonBlackList_; } // Return the custom emoticon code for an msn object QString ContactBase::getEmoticonCode(const QString &msnObjectDataHash) const { return pendingEmoticons_[ msnObjectDataHash ]; } // Return the custom emoticon search pattern. const QRegExp & ContactBase::getEmoticonPattern() const { return emoticonRegExp_; } // Return the custom emoticon replacements. const QHash<QString,QString>& ContactBase::getEmoticonReplacements() const { return customEmoticons_; } // Return the contact's friendly name QString ContactBase::getFriendlyName( FormattingMode mode ) const { return friendlyName_.getString( mode ); } // Return the contact's handle QString ContactBase::getHandle() const { return handle_; } // Return the search pattern for pending custom emoticons. const QRegExp & ContactBase::getPendingEmoticonPattern() const { return pendingRegExp_; } // Return the list of switchboard connections const QList<const MsnSwitchboardConnection*> ContactBase::getSwitchboardConnections() const { return chatSessions_; } // Returns whether the contact has an application list initialized. bool ContactBase::hasApplicationList() const { return (applicationList_ != 0); } // Returns whether the contact has capability bool ContactBase::hasCapability( MsnClientCapabilities flag ) const { return ( capabilities_ & flag ) == flag; } // Returns whether the contact's client supports the given MSNP2P version bool ContactBase::hasP2PSupport( MsnClientCapabilities minimalVersion ) const { #ifdef KMESSTEST KMESS_ASSERT( minimalVersion != 0 && (minimalVersion & 0x0fffffff) == 0 ); // don't pass some other capability. #endif return capabilities_ >= (uint) minimalVersion; } // Return true if the contact is offline bool ContactBase::isOffline() const { return getStatus() == STATUS_OFFLINE; } // Return true if the contact is online bool ContactBase::isOnline() const { return getStatus() != STATUS_OFFLINE; } // Add or remove a custom emoticon from the black list bool ContactBase::manageEmoticonBlackList( bool add, const QString &emoticonCode ) { // Add the emoticon if( add ) { // Don't duplicate emoticons in the list if( emoticonBlackList_.contains( emoticonCode ) ) { return false; } emoticonBlackList_.append( emoticonCode ); return true; } // Remove the emoticon else { return ( emoticonBlackList_.removeAll( emoticonCode ) > 0 ); } } // Unregister the contact as participant, contact has left the chat. void ContactBase::removeSwitchboardConnection( const MsnSwitchboardConnection *connection, bool userInitiated ) { #ifdef KMESSDEBUG_CONTACTBASE kDebug() << "removing connection, userInitiated=" << userInitiated; #endif bool found = chatSessions_.removeAll( connection ) > 0; if( ! found ) { kWarning() << "chat session not found."; } if( chatSessions_.isEmpty() ) { #ifdef KMESSDEBUG_CONTACTBASE kDebug() << handle_ << " is no longer in any other chat conversation, cleaning up."; #endif // Inform the ApplicationList if we have one. // It's possible some sessions need to be terminated now. if( applicationList_ != 0 ) { bool hasAbortingApps = applicationList_->contactLeftChat( userInitiated ); // If there aren't any applications aborting, the object can be deleted now. // Also avoid deleting the object if there is still a direct connection. // It's possible a switchboard is replaced with another one (with WLM -> background chat -> normal chat). // Note the object could be destroyed from slotApplicationListAborted() too. if( ! hasAbortingApps && applicationList_ != 0 && ! applicationList_->hasAuthorizedDirectConnection() ) { #ifdef KMESSDEBUG_CONTACTBASE kDebug() << "no applications to abort, deleting ApplicationList object."; #endif delete applicationList_; applicationList_ = 0; } } // This will be used by CurrentAccount to clean up temporary InvitedContact objects. emit leftAllChats( this ); } } // Set the client capabilities void ContactBase::setCapabilities( uint capabilities ) { // See if the client software could have changed. // Ignore flags which can be toggled by the client. const int toggleFlags = ( MSN_CAP_VIDEO_CHAT | MSN_CAP_MSN_MOBILE | MSN_CAP_MSN_DIRECT | MSN_CAP_LIVE_SPACE | MSN_CAP_ONECARE ); bool clientChanged = ( capabilities_ | toggleFlags ) != ( capabilities | toggleFlags ); // Reset the client name information if the capabilities changed. if( clientChanged ) { #ifdef KMESSDEBUG_CONTACTBASE kDebug() << "capabilities of" << handle_ << "changed from" << capabilities_ << "to" << capabilities << ", resetting detected app name."; #endif clientAppName_.clear(); clientName_.clear(); } // Set caps capabilities_ = capabilities; } // Restore the user readable client application name void ContactBase::setClientAppName( const QString &clientAppName ) { clientAppName_ = clientAppName; } // Set the third part client name field void ContactBase::setClientName( const QString& clientName ) { clientName_ = clientName; // Also give a more precise real name clientAppName_ = clientName_.replace("/", " "); // Weird 'Purple 2.3.1' string means the user actually has Pidgin or Adium. if( clientAppName_.startsWith("Purple ") ) { clientAppName_ = "Pidgin/Adium (" + clientAppName_ + ")"; } } // The application list completed aborting it's applications. void ContactBase::slotApplicationListAborted() { // Avoid killing an object if it was filled with new entries again if( ! applicationList_->isEmpty() || applicationList_->hasAuthorizedDirectConnection() ) { #ifdef KMESSDEBUG_CONTACTBASE kDebug() << "requested abortion completed, but ApplicationList object has object references: not deleting object."; #endif return; } #ifdef KMESSDEBUG_CONTACTBASE kDebug() << "requested abortion completed, deleting ApplicationList object."; #endif // Use deleteLater() because this is called from the signal loop. applicationList_->deleteLater(); applicationList_ = 0; } #include "contactbase.moc"