Port things from MSN to WLM plugin:
[kdenetwork.git] / kopete / protocols / oscar / oscarcontact.cpp
blob17c811bc27984e035f0672433710ffe20dad23e6
1 /*
2 oscarcontact.cpp - Oscar Protocol Plugin
4 Copyright (c) 2002 by Tom Linsky <twl6@po.cwru.edu>
5 Kopete (c) 2002-2008 by the Kopete developers <kopete-devel@kde.org>
7 *************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 *************************************************************************
17 #include "oscarcontact.h"
19 #include <time.h>
21 #include <qapplication.h>
22 #include <qtextcodec.h>
23 #include <qtimer.h>
25 #include <kaction.h>
26 #include <kdebug.h>
27 #include <klocale.h>
28 #include <krandom.h>
29 #include <kcodecs.h>
30 #include <kmessagebox.h>
31 #include <kstandarddirs.h>
32 #include <kinputdialog.h>
34 #include <kdeversion.h>
35 #include <kfiledialog.h>
37 #include "kopeteaccount.h"
38 #include "kopetechatsessionmanager.h"
39 #include "kopetemetacontact.h"
40 #include "kopetecontactlist.h"
41 #include "kopetegroup.h"
42 #include "kopeteuiglobal.h"
43 #include <kopeteglobal.h>
44 #include "kopetetransfermanager.h"
45 #include "kopeteavatarmanager.h"
47 #include "oscaraccount.h"
48 #include "client.h"
49 #include "contactmanager.h"
50 #include "oscarutils.h"
51 #include "oscarprotocol.h"
52 #include "oscarencodingselectiondialog.h"
53 #include "oscarstatusmanager.h"
54 #include "filetransferhandler.h"
56 #include <assert.h>
58 OscarContact::OscarContact( Kopete::Account* account, const QString& name,
59 Kopete::MetaContact* parent, const QString& icon )
60 : Kopete::Contact( account, name, parent, icon )
62 mAccount = static_cast<OscarAccount*>(account);
63 mName = name;
64 mMsgManager = 0L;
65 m_buddyIconDirty = false;
66 m_oesd = 0;
68 setFileCapable( true );
70 QObject::connect( mAccount->engine(), SIGNAL(haveIconForContact(const QString&, QByteArray)),
71 this, SLOT(haveIcon(const QString&, QByteArray)) );
72 QObject::connect( mAccount->engine(), SIGNAL(iconServerConnected()),
73 this, SLOT(requestBuddyIcon()) );
74 QObject::connect( mAccount->engine(), SIGNAL(receivedAwayMessage(const QString&, const QString& )),
75 this, SLOT(receivedStatusMessage(const QString&, const QString&)) );
76 QObject::connect( mAccount->engine(), SIGNAL(messageAck(const QString&, uint)),
77 this, SLOT(messageAck(const QString&, uint)) );
78 QObject::connect( mAccount->engine(), SIGNAL(messageError(const QString&, uint)),
79 this, SLOT(messageError(const QString&, uint)) );
82 OscarContact::~OscarContact()
86 void OscarContact::serialize(QMap<QString, QString> &serializedData,
87 QMap<QString, QString> &/*addressBookData*/)
89 serializedData["ssi_name"] = m_ssiItem.name();
90 serializedData["ssi_type"] = QString::number( m_ssiItem.type() );
91 serializedData["ssi_gid"] = QString::number( m_ssiItem.gid() );
92 serializedData["ssi_bid"] = QString::number( m_ssiItem.bid() );
93 serializedData["ssi_alias"] = m_ssiItem.alias();
94 serializedData["ssi_waitingAuth"] = m_ssiItem.waitingAuth() ? QString::fromLatin1( "true" ) : QString::fromLatin1( "false" );
95 serializedData["ssi_metaInfoId"] = m_ssiItem.metaInfoId().toHex();
98 bool OscarContact::isOnServer() const
100 ContactManager* serverList = mAccount->engine()->ssiManager();
101 OContact ssi = serverList->findContact( Oscar::normalize( contactId() ) );
103 return ( ssi && ssi.type() != 0xFFFF );
106 void OscarContact::setSSIItem( const OContact& ssiItem )
108 if ( !ssiItem.alias().isEmpty() )
109 setProperty( Kopete::Global::Properties::self()->nickName(), ssiItem.alias() );
111 m_ssiItem = ssiItem;
114 OContact OscarContact::ssiItem() const
116 return m_ssiItem;
119 Kopete::ChatSession* OscarContact::manager( CanCreateFlags canCreate )
121 if ( !mMsgManager && canCreate )
123 /*kDebug(14190) <<
124 "Creating new ChatSession for contact '" << displayName() << "'" << endl;*/
126 QList<Kopete::Contact*> theContact;
127 theContact.append(this);
129 mMsgManager = Kopete::ChatSessionManager::self()->create(account()->myself(), theContact, protocol());
131 // This is for when the user types a message and presses send
132 connect(mMsgManager, SIGNAL( messageSent( Kopete::Message&, Kopete::ChatSession * ) ),
133 this, SLOT( slotSendMsg( Kopete::Message&, Kopete::ChatSession * ) ) );
135 // For when the message manager is destroyed
136 connect(mMsgManager, SIGNAL( destroyed() ),
137 this, SLOT( chatSessionDestroyed() ) );
139 connect(mMsgManager, SIGNAL( myselfTyping( bool ) ),
140 this, SLOT( slotTyping( bool ) ) );
142 return mMsgManager;
145 void OscarContact::deleteContact()
147 mAccount->engine()->removeContact( contactId() );
148 deleteLater();
151 void OscarContact::chatSessionDestroyed()
153 mMsgManager = 0L;
156 // Called when the metacontact owning this contact has changed groups
157 void OscarContact::sync(unsigned int flags)
160 * If the contact has changed groups, then we update the server
161 * adding the group if it doesn't exist, changing the ssi item
162 * contained in the client and updating the contact's ssi item
163 * Otherwise, we don't do much
166 if( !metaContact() || metaContact()->isTemporary() )
167 return;
169 if ( (flags & Kopete::Contact::MovedBetweenGroup) == Kopete::Contact::MovedBetweenGroup )
172 kDebug(OSCAR_GEN_DEBUG) << "Moving a contact between groups";
173 ContactManager* ssiManager = mAccount->engine()->ssiManager();
175 OContact oldGroup = ssiManager->findGroup( m_ssiItem.gid() );
176 Kopete::Group* newGroup = metaContact()->groups().first();
177 if ( newGroup->displayName() == oldGroup.name() )
178 return; //we didn't really move
180 if ( m_ssiItem.isValid() )
181 mAccount->changeContactGroupInSSI( contactId(), newGroup->displayName(), true );
182 else
183 mAccount->addContactToSSI( contactId(), newGroup->displayName(), true );
185 return;
188 void OscarContact::userInfoUpdated( const QString& contact, const UserDetails& details )
190 Q_UNUSED( contact );
192 if ( details.buddyIconHash().size() > 0 && details.buddyIconHash() != m_details.buddyIconHash() )
194 OscarProtocol *p = static_cast<OscarProtocol*>(protocol());
195 if ( property( p->buddyIconHash ).value().toByteArray() != details.buddyIconHash() )
197 m_buddyIconDirty = true;
199 if ( !mAccount->engine()->hasIconConnection() )
201 mAccount->engine()->connectToIconServer();
203 else
205 int time = ( KRandom::random() % 10 ) * 1000;
206 kDebug(OSCAR_GEN_DEBUG) << "updating buddy icon in "
207 << time/1000 << " seconds" << endl;
208 QTimer::singleShot( time, this, SLOT( requestBuddyIcon() ) );
213 setProperty( Kopete::Global::Properties::self()->onlineSince(), details.onlineSinceTime() );
214 setIdleTime( details.idleTime() );
215 m_warningLevel = details.warningLevel();
216 m_details.merge( details );
218 setFileCapable( m_details.hasCap( CAP_SENDFILE ) );
220 QStringList capList;
221 // Append client name and version in case we found one
222 //if ( m_details.userClass() & 0x0080 /* WIRELESS */ )
223 // capList << i18n( "Mobile AIM Client" );
224 //else
226 // if ( !m_details.clientName().isEmpty() )
227 // {
228 // capList << i18nc( "Translators: client name and version",
229 // "%1", m_details.clientName() );
230 // }
233 // and now for some general informative capabilities
234 if ( m_details.hasCap( CAP_BUDDYICON ) )
235 capList << i18n( "Buddy icons" );
236 if ( m_details.hasCap( CAP_UTF8 ) )
237 capList << i18n( "UTF-8" );
238 if ( m_details.hasCap( CAP_RTFMSGS ) )
239 capList << i18n( "Rich text messages" );
240 if ( m_details.hasCap( CAP_CHAT ) )
241 capList << i18n( "Group chat" );
242 if ( m_details.hasCap( CAP_VOICE ) )
243 capList << i18n( "Voice chat" );
244 if ( m_details.hasCap( CAP_IMIMAGE ) )
245 capList << i18n( "DirectIM/IMImage" );
246 if ( m_details.hasCap( CAP_SENDBUDDYLIST ) )
247 capList << i18n( "Send buddy list" );
248 if ( m_details.hasCap( CAP_SENDFILE ) )
249 capList << i18n( "File transfers" );
250 if ( m_details.hasCap( CAP_GAMES ) || m_details.hasCap( CAP_GAMES2 ) )
251 capList << i18n( "Games" );
253 m_clientFeatures = capList.join( ", " );
254 setProperty( static_cast<OscarProtocol*>(protocol())->clientFeatures, m_clientFeatures );
256 setProperty( static_cast<OscarProtocol*>(protocol())->memberSince, details.memberSinceTime() );
257 setProperty( static_cast<OscarProtocol*>(protocol())->client, details.clientName() );
258 setProperty( static_cast<OscarProtocol*>(protocol())->protocolVersion, QString::number(details.dcProtoVersion()) );
261 void OscarContact::startedTyping()
263 if ( mMsgManager )
264 mMsgManager->receivedTypingMsg( this, true );
267 void OscarContact::stoppedTyping()
269 if ( mMsgManager )
270 mMsgManager->receivedTypingMsg( this, false );
273 void OscarContact::slotTyping( bool typing )
275 if ( this != account()->myself() )
276 account()->engine()->sendTyping( contactId(), typing );
279 void OscarContact::messageAck( const QString& contact, uint messageId )
281 if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
282 return;
284 Kopete::ChatSession* chatSession = manager();
285 if ( chatSession )
286 chatSession->receivedMessageState( messageId, Kopete::Message::StateSent );
289 void OscarContact::messageError( const QString& contact, uint messageId )
291 if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
292 return;
294 Kopete::ChatSession* chatSession = manager();
295 if ( chatSession )
296 chatSession->receivedMessageState( messageId, Kopete::Message::StateError );
299 QTextCodec* OscarContact::contactCodec() const
301 if ( hasProperty( "contactEncoding" ) )
303 QTextCodec* codec = QTextCodec::codecForMib( property( "contactEncoding" ).value().toInt() );
305 if ( codec )
306 return codec;
307 else
308 return QTextCodec::codecForMib( 4 );
310 else
311 return mAccount->defaultCodec();
314 bool OscarContact::hasCap( int capNumber ) const
316 return m_details.hasCap( capNumber );
319 void OscarContact::setPresenceTarget( const Oscar::Presence &presence )
321 OscarProtocol* p = static_cast<OscarProtocol *>(protocol());
322 setOnlineStatus( p->statusManager()->onlineStatusOf( presence ) );
325 void OscarContact::setEncoding( int mib )
327 OscarProtocol* p = static_cast<OscarProtocol*>( protocol() );
328 if ( mib != 0 )
330 kDebug(OSCAR_GEN_DEBUG) << "setting encoding mib to " << mib << endl;
331 setProperty( p->contactEncoding, m_oesd->selectedEncoding() );
333 else
335 kDebug(OSCAR_GEN_DEBUG) << "setting encoding to default" << endl;
336 removeProperty( p->contactEncoding );
340 //here's where a filetransfer usually begins
341 //could be called by a KAction or our dcop code or something
342 void OscarContact::sendFile( const KUrl &sourceURL, const QString &altFileName, uint fileSize )
344 kDebug(OSCAR_GEN_DEBUG) << "file: '" << sourceURL
345 << "' '" << altFileName << "' size " << fileSize << endl;
346 QStringList files;
348 //If the file location is null, then get it from a file open dialog
349 if( !sourceURL.isValid() )
350 files = KFileDialog::getOpenFileNames( KUrl() ,"*", 0l , i18n( "Kopete File Transfer" ));
351 else
352 files << sourceURL.path(KUrl::RemoveTrailingSlash);
354 if( files.isEmpty() )
356 kDebug(OSCAR_GEN_DEBUG) << "files empty, assuming cancel";
357 return;
359 kDebug(OSCAR_GEN_DEBUG) << "files: '" << files << "' ";
361 FileTransferHandler *ftHandler = mAccount->engine()->createFileTransfer( mName, files );
363 Kopete::TransferManager *transferManager = Kopete::TransferManager::transferManager();
364 Kopete::Transfer *transfer = transferManager->addTransfer( this, files, ftHandler->totalSize(), mName, Kopete::FileTransferInfo::Outgoing);
366 connect( transfer, SIGNAL(transferCanceled()), ftHandler, SLOT(cancel()) );
368 connect( ftHandler, SIGNAL(transferCancelled()), transfer, SLOT(slotCancelled()) );
369 connect( ftHandler, SIGNAL(transferError(int, const QString&)), transfer, SLOT(slotError(int, const QString&)) );
370 connect( ftHandler, SIGNAL(transferProcessed(unsigned int)), transfer, SLOT(slotProcessed(unsigned int)) );
371 connect( ftHandler, SIGNAL(transferFinished()), transfer, SLOT(slotComplete()) );
372 connect( ftHandler, SIGNAL(transferNextFile(const QString&, const QString&)),
373 transfer, SLOT(slotNextFile(const QString&, const QString&)) );
375 ftHandler->send();
378 void OscarContact::setAwayMessage( const QString &message )
380 kDebug(OSCAR_AIM_DEBUG) <<
381 "Called for '" << contactId() << "', away msg='" << message << "'" << endl;
383 if ( !message.isEmpty() )
384 setProperty( static_cast<OscarProtocol*>( protocol() )->statusMessage, filterAwayMessage( message ) );
385 else
386 removeProperty( static_cast<OscarProtocol*>( protocol() )->statusMessage );
388 emit statusMessageChanged();
391 void OscarContact::changeContactEncoding()
393 if ( m_oesd )
394 return;
396 OscarProtocol* p = static_cast<OscarProtocol*>( protocol() );
397 m_oesd = new OscarEncodingSelectionDialog( Kopete::UI::Global::mainWidget(), property(p->contactEncoding).value().toInt() );
398 connect( m_oesd, SIGNAL(closing(int)), this, SLOT(changeEncodingDialogClosed(int)) );
399 m_oesd->show();
402 void OscarContact::requestAuthorization()
404 QString info = i18n("The user %1 requires authorization before being added to a contact list. "
405 "Do you want to send an authorization request?\n\nReason for requesting authorization:",
406 ( !nickName().isEmpty() ) ? nickName() : contactId() );
408 QString reason = KInputDialog::getText( i18n("Request Authorization"), info,
409 i18n("Please authorize me so I can add you to my contact list") );
410 if ( !reason.isNull() )
411 mAccount->engine()->requestAuth( contactId(), reason );
414 void OscarContact::changeEncodingDialogClosed( int result )
416 if ( result == QDialog::Accepted )
417 setEncoding( m_oesd->selectedEncoding() );
419 if ( m_oesd )
421 m_oesd->deleteLater();
422 m_oesd = 0L;
426 void OscarContact::requestBuddyIcon()
428 if ( m_buddyIconDirty && m_details.buddyIconHash().size() > 0 )
430 account()->engine()->requestBuddyIcon( contactId(), m_details.buddyIconHash(),
431 m_details.iconType(), m_details.iconCheckSumType() );
435 void OscarContact::haveIcon( const QString& user, QByteArray icon )
437 if ( Oscar::normalize( user ) != Oscar::normalize( contactId() ) )
438 return;
440 kDebug(OSCAR_GEN_DEBUG) << "Updating icon for " << contactId();
442 KMD5 buddyIconHash( icon );
443 if ( memcmp( buddyIconHash.rawDigest(), m_details.buddyIconHash().data(), 16 ) == 0 )
445 QImage img;
446 img.loadFromData(icon);
447 Kopete::AvatarManager::AvatarEntry entry;
448 entry.name = contactId();
449 entry.category = Kopete::AvatarManager::Contact;
450 entry.contact = this;
451 entry.image = img;
452 entry = Kopete::AvatarManager::self()->add(entry);
454 setProperty( static_cast<OscarProtocol*>(protocol())->buddyIconHash, m_details.buddyIconHash() );
455 if (!entry.path.isNull())
457 removeProperty( Kopete::Global::Properties::self()->photo() );
458 setProperty( Kopete::Global::Properties::self()->photo(), entry.path );
461 m_buddyIconDirty = false;
463 else
465 kDebug(14153) << "Buddy icon hash does not match!";
466 removeProperty( Kopete::Global::Properties::self()->photo() );
470 void OscarContact::receivedStatusMessage( const QString& contact, const QString& message )
472 if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
473 return;
475 setAwayMessage( message );
478 QString OscarContact::filterAwayMessage( const QString &message ) const
480 QString filteredMessage = message;
481 filteredMessage.replace(
482 QRegExp(QString::fromLatin1("<[hH][tT][mM][lL].*>(.*)</[hH][tT][mM][lL]>")),
483 QString::fromLatin1("\\1"));
484 filteredMessage.replace(
485 QRegExp(QString::fromLatin1("<[bB][oO][dD][yY].*>(.*)</[bB][oO][dD][yY]>")),
486 QString::fromLatin1("\\1") );
487 QRegExp fontRemover( QString::fromLatin1("<[fF][oO][nN][tT].*>(.*)</[fF][oO][nN][tT]>") );
488 fontRemover.setMinimal(true);
489 while ( filteredMessage.indexOf( fontRemover ) != -1 )
490 filteredMessage.replace( fontRemover, QString::fromLatin1("\\1") );
491 return filteredMessage;
494 #include "oscarcontact.moc"
495 //kate: tab-width 4; indent-mode csands;