1 /*
2   oscarcontact.cpp  -  Oscar Protocol Plugin
3 
4   Copyright (c) 2002 by Tom Linsky <twl6@po.cwru.edu>
5   Kopete    (c) 2002-2008 by the Kopete developers  <kopete-devel@kde.org>
6 
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   *************************************************************************
15 */
16 
17 #include "oscarcontact.h"
18 
19 #include <time.h>
20 
21 #include <qapplication.h>
22 #include <qtextcodec.h>
23 #include <qtimer.h>
24 #include <QCryptographicHash>
25 #include <QDomDocument>
26 #include <QDomNodeList>
27 #include <QTextDocument>
28 #include <QTextCharFormat>
29 #include <QTextBlock>
30 
31 #include <QAction>
32 #include <kdebug.h>
33 #include <KLocalizedString>
34 #include <krandom.h>
35 #include <kcodecs.h>
36 #include <kmessagebox.h>
37 
38 #include <qinputdialog.h>
39 
40 #include <qfiledialog.h>
41 
42 #include "kopeteaccount.h"
43 #include "kopetechatsessionmanager.h"
44 #include "kopetemetacontact.h"
45 #include "kopetecontactlist.h"
46 #include "kopetegroup.h"
47 #include "kopeteuiglobal.h"
48 #include <kopeteglobal.h>
49 #include "kopetetransfermanager.h"
50 #include "kopeteavatarmanager.h"
51 
52 #include "oscaraccount.h"
53 #include "client.h"
54 #include "contactmanager.h"
55 #include "oscarutils.h"
56 #include "oscarprotocol.h"
57 #include "oscarencodingselectiondialog.h"
58 #include "oscarstatusmanager.h"
59 #include "filetransferhandler.h"
60 
61 #include <assert.h>
62 
OscarContact(Kopete::Account * account,const QString & name,Kopete::MetaContact * parent,const QString & icon)63 OscarContact::OscarContact( Kopete::Account* account, const QString& name,
64                             Kopete::MetaContact* parent, const QString& icon )
65 : Kopete::Contact( account, name, parent, icon )
66 {
67 	mAccount = static_cast<OscarAccount*>(account);
68 	mName = name;
69 	mMsgManager = nullptr;
70 	m_buddyIconDirty = false;
71 	m_oesd = 0;
72 
73 	setFileCapable( true );
74 
75 	QObject::connect( mAccount->engine(), SIGNAL(haveIconForContact(QString,QByteArray)),
76 	                  this, SLOT(haveIcon(QString,QByteArray)) );
77 	QObject::connect( mAccount->engine(), SIGNAL(iconServerConnected()),
78 	                  this, SLOT(requestBuddyIcon()) );
79 	QObject::connect( mAccount->engine(), SIGNAL(receivedAwayMessage(QString,QString)),
80 	                  this, SLOT(receivedStatusMessage(QString,QString)) );
81 	QObject::connect( mAccount->engine(), SIGNAL(messageAck(QString,uint)),
82 	                  this, SLOT(messageAck(QString,uint)) );
83 	QObject::connect( mAccount->engine(), SIGNAL(messageError(QString,uint)),
84 	                  this, SLOT(messageError(QString,uint)) );
85 }
86 
~OscarContact()87 OscarContact::~OscarContact()
88 {
89 }
90 
serialize(QMap<QString,QString> & serializedData,QMap<QString,QString> &)91 void OscarContact::serialize(QMap<QString, QString> &serializedData,
92                              QMap<QString, QString> &/*addressBookData*/)
93 {
94 	serializedData["ssi_name"] = m_ssiItem.name();
95 	serializedData["ssi_type"] = QString::number( m_ssiItem.type() );
96 	serializedData["ssi_gid"] = QString::number( m_ssiItem.gid() );
97 	serializedData["ssi_bid"] = QString::number( m_ssiItem.bid() );
98 	serializedData["ssi_alias"] = m_ssiItem.alias();
99 	serializedData["ssi_waitingAuth"] = m_ssiItem.waitingAuth() ? QString::fromLatin1( "true" ) : QString::fromLatin1( "false" );
100 	serializedData["ssi_metaInfoId"] = m_ssiItem.metaInfoId().toHex();
101 }
102 
isOnServer() const103 bool OscarContact::isOnServer() const
104 {
105     ContactManager* serverList = mAccount->engine()->ssiManager();
106 	OContact ssi = serverList->findContact( Oscar::normalize( contactId() ) );
107 
108 	return ( ssi && ssi.type() != 0xFFFF );
109 }
110 
setSSIItem(const OContact & ssiItem)111 void OscarContact::setSSIItem( const OContact& ssiItem )
112 {
113 	setCustomName( ssiItem.alias() );
114 	m_ssiItem = ssiItem;
115 }
116 
ssiItem() const117 OContact OscarContact::ssiItem() const
118 {
119 	return m_ssiItem;
120 }
121 
manager(CanCreateFlags canCreate)122 Kopete::ChatSession* OscarContact::manager( CanCreateFlags canCreate )
123 {
124 	if ( !mMsgManager && canCreate )
125 	{
126 		/*kDebug(14190) <<
127 			"Creating new ChatSession for contact '" << displayName() << "'" << endl;*/
128 
129 		QList<Kopete::Contact*> theContact;
130 		theContact.append(this);
131 
132 		mMsgManager = Kopete::ChatSessionManager::self()->create(account()->myself(), theContact, protocol());
133 
134 		// This is for when the user types a message and presses send
135 		connect(mMsgManager, SIGNAL(messageSent(Kopete::Message&,Kopete::ChatSession*)),
136 		        this, SLOT(slotSendMsg(Kopete::Message&,Kopete::ChatSession*)) );
137 
138 		// For when the message manager is destroyed
139 		connect(mMsgManager, SIGNAL(destroyed()),
140 		        this, SLOT(chatSessionDestroyed()) );
141 
142 		connect(mMsgManager, SIGNAL(myselfTyping(bool)),
143 		        this, SLOT(slotTyping(bool)) );
144 	}
145 	return mMsgManager;
146 }
147 
deleteContact()148 void OscarContact::deleteContact()
149 {
150 	mAccount->engine()->removeContact( contactId() );
151 	deleteLater();
152 }
153 
chatSessionDestroyed()154 void OscarContact::chatSessionDestroyed()
155 {
156 	mMsgManager = nullptr;
157 }
158 
159 // Called when the metacontact owning this contact has changed groups
sync(unsigned int flags)160 void OscarContact::sync(unsigned int flags)
161 {
162 	/*
163 	 * If the contact has changed groups, then we update the server
164 	 *   adding the group if it doesn't exist, changing the ssi item
165 	 *   contained in the client and updating the contact's ssi item
166 	 * Otherwise, we don't do much
167 	 */
168 
169 	if( !metaContact() || metaContact()->isTemporary() )
170 		return;
171 
172 	if ( (flags & Kopete::Contact::MovedBetweenGroup) == Kopete::Contact::MovedBetweenGroup )
173 	{
174 
175 		kDebug(OSCAR_GEN_DEBUG) << "Moving a contact between groups";
176 		ContactManager* ssiManager = mAccount->engine()->ssiManager();
177 
178 		OContact oldGroup = ssiManager->findGroup( m_ssiItem.gid() );
179 		Kopete::Group* newGroup = metaContact()->groups().first();
180 		QString newGroupName = newGroup->displayName();
181 		if ( newGroup->type() == Kopete::Group::TopLevel )
182 			newGroupName = "Buddies";
183 		if ( newGroupName == oldGroup.name() )
184 			return; //we didn't really move
185 
186 		if ( m_ssiItem.isValid() )
187 			mAccount->changeContactGroupInSSI( contactId(), newGroupName, true );
188 		else
189 			mAccount->addContactToSSI( contactId(), newGroupName, true );
190 	}
191 
192 	if ( flags & Kopete::Contact::DisplayNameChanged && mAccount->engine() )
193 	{
194 		kDebug(OSCAR_GEN_DEBUG) << "Changing contact alias";
195 		mAccount->engine()->changeContactAlias( contactId(), metaContact()->displayName() );
196 	}
197 }
198 
userInfoUpdated(const QString & contact,const UserDetails & details)199 void OscarContact::userInfoUpdated( const QString& contact, const UserDetails& details  )
200 {
201 	Q_UNUSED( contact );
202 
203 	if ( details.buddyIconHash().size() > 0 && details.buddyIconHash() != m_details.buddyIconHash() )
204 	{
205 		OscarProtocol *p = static_cast<OscarProtocol*>(protocol());
206 		QString photoPath = property( Kopete::Global::Properties::self()->photo() ).value().toString();
207 		if ( property( p->buddyIconHash ).value().toByteArray() != details.buddyIconHash() || QFileInfo(photoPath).size() == 0 )
208 		{
209 			m_buddyIconDirty = true;
210 
211 			if ( !mAccount->engine()->hasIconConnection() )
212 			{
213 				mAccount->engine()->connectToIconServer();
214 			}
215 			else
216 			{
217 				int time = ( KRandom::random() % 10 ) * 1000;
218 				kDebug(OSCAR_GEN_DEBUG) << "updating buddy icon in "
219 					<< time/1000 << " seconds" << endl;
220 				QTimer::singleShot( time, this, SLOT(requestBuddyIcon()) );
221 			}
222 		}
223 	}
224 
225 	setProperty( Kopete::Global::Properties::self()->onlineSince(), details.onlineSinceTime() );
226 	setIdleTime( details.idleTime() );
227 	m_warningLevel = details.warningLevel();
228 	m_details.merge( details );
229 
230 	setFileCapable( m_details.hasCap( CAP_SENDFILE ) );
231 
232 	QStringList capList;
233 	// Append client name and version in case we found one
234 	//if ( m_details.userClass() & 0x0080 /* WIRELESS */ )
235 	//	capList << i18n( "Mobile AIM Client" );
236 	//else
237 	//{
238 	//	if ( !m_details.clientName().isEmpty() )
239 	//	{
240 	//		capList << i18nc( "Translators: client name and version",
241 	//		                "%1", m_details.clientName() );
242 	//	}
243 	//}
244 
245 	// and now for some general informative capabilities
246 	if ( m_details.hasCap( CAP_BUDDYICON ) )
247 		capList << i18n( "Buddy icons" );
248 	if ( m_details.hasCap( CAP_UTF8 ) )
249 		capList << i18n( "UTF-8" );
250 	if ( m_details.hasCap( CAP_RTFMSGS ) )
251 		capList << i18n( "Rich text messages" );
252 	if ( m_details.hasCap( CAP_CHAT ) )
253 		capList << i18n( "Group chat" );
254 	if ( m_details.hasCap( CAP_VOICE ) )
255 		capList << i18n( "Voice chat" );
256 	if ( m_details.hasCap( CAP_IMIMAGE ) )
257 		capList << i18n( "DirectIM/IMImage" );
258 	if ( m_details.hasCap( CAP_SENDBUDDYLIST ) )
259 		capList << i18n( "Send buddy list" );
260 	if ( m_details.hasCap( CAP_SENDFILE ) )
261 		capList << i18n( "File transfers" );
262 	if ( m_details.hasCap( CAP_GAMES ) || m_details.hasCap( CAP_GAMES2 ) )
263 		capList << i18n( "Games" );
264 
265 	m_clientFeatures = capList.join( ", " );
266 	setProperty( static_cast<OscarProtocol*>(protocol())->clientFeatures, m_clientFeatures );
267 
268 	setProperty( static_cast<OscarProtocol*>(protocol())->memberSince, details.memberSinceTime() );
269 	setProperty( static_cast<OscarProtocol*>(protocol())->client, details.clientName() );
270 	setProperty( static_cast<OscarProtocol*>(protocol())->protocolVersion, QString::number(details.dcProtoVersion()) );
271 }
272 
startedTyping()273 void OscarContact::startedTyping()
274 {
275 	Kopete::ChatSession* cs = manager();
276 	// We want the user to know if someone is typing a message
277 	// but there is no chat session for this contact
278 	cs->receivedTypingMsg( this, true );
279 }
280 
stoppedTyping()281 void OscarContact::stoppedTyping()
282 {
283 	if ( mMsgManager )
284 		mMsgManager->receivedTypingMsg( this, false );
285 }
286 
slotTyping(bool typing)287 void OscarContact::slotTyping( bool typing )
288 {
289 	if ( this != account()->myself() )
290 		account()->engine()->sendTyping( contactId(), typing );
291 }
292 
messageAck(const QString & contact,uint messageId)293 void OscarContact::messageAck( const QString& contact, uint messageId )
294 {
295 	if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
296 		return;
297 
298 	Kopete::ChatSession* chatSession = manager();
299 	if ( chatSession )
300 		chatSession->receivedMessageState( messageId, Kopete::Message::StateSent );
301 }
302 
messageError(const QString & contact,uint messageId)303 void OscarContact::messageError( const QString& contact, uint messageId )
304 {
305 	if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
306 		return;
307 
308 	Kopete::ChatSession* chatSession = manager();
309 	if ( chatSession )
310 		chatSession->receivedMessageState( messageId, Kopete::Message::StateError );
311 }
312 
contactCodec() const313 QTextCodec* OscarContact::contactCodec() const
314 {
315 	if ( hasProperty( "contactEncoding" ) )
316 	{
317 		QTextCodec* codec = QTextCodec::codecForMib( property( "contactEncoding" ).value().toInt() );
318 
319 		if ( codec )
320 			return codec;
321 		else
322 			return QTextCodec::codecForMib( 4 );
323 	}
324 	else
325 		return mAccount->defaultCodec();
326 }
327 
hasCap(int capNumber) const328 bool OscarContact::hasCap( int capNumber ) const
329 {
330 	return m_details.hasCap( capNumber );
331 }
332 
setPresenceTarget(const Oscar::Presence & presence)333 void OscarContact::setPresenceTarget( const Oscar::Presence &presence )
334 {
335 	OscarProtocol* p = static_cast<OscarProtocol *>(protocol());
336 	setOnlineStatus( p->statusManager()->onlineStatusOf( presence ) );
337 }
338 
setEncoding(int mib)339 void OscarContact::setEncoding( int mib )
340 {
341 	OscarProtocol* p = static_cast<OscarProtocol*>( protocol() );
342 	if ( mib != 0 )
343 	{
344 		kDebug(OSCAR_GEN_DEBUG) << "setting encoding mib to " << mib << endl;
345 		setProperty( p->contactEncoding, m_oesd->selectedEncoding() );
346 	}
347 	else
348 	{
349 		kDebug(OSCAR_GEN_DEBUG) << "setting encoding to default" << endl;
350 		removeProperty( p->contactEncoding );
351 	}
352 }
353 
354 //here's where a filetransfer usually begins
355 //could be called by a QAction or our dcop code or something
sendFile(const QUrl & sourceURL,const QString & altFileName,uint fileSize)356 void OscarContact::sendFile( const QUrl &sourceURL, const QString &altFileName, uint fileSize )
357 {
358 	kDebug(OSCAR_GEN_DEBUG) << "file: '" << sourceURL
359 		<< "' '" << altFileName << "' size " << fileSize << endl;
360 	QStringList files;
361 
362 	//If the file location is null, then get it from a file open dialog
363     if( !sourceURL.isValid() ) {
364         files = QFileDialog::getOpenFileNames( nullptr, i18n( "Kopete File Transfer" ), QString() , "*");
365     } else {
366         //FIXME KF5 files << sourceURL.path(KUrl::RemoveTrailingSlash);
367     }
368 
369 	if( files.isEmpty() )
370 	{
371 		kDebug(OSCAR_GEN_DEBUG) << "files empty, assuming cancel";
372 		return;
373 	}
374 	kDebug(OSCAR_GEN_DEBUG) << "files: '" << files << "' ";
375 
376 	FileTransferHandler *ftHandler = mAccount->engine()->createFileTransfer( mName, files );
377 
378 	Kopete::TransferManager *transferManager = Kopete::TransferManager::transferManager();
379 	Kopete::Transfer *transfer = transferManager->addTransfer( this, files, ftHandler->totalSize(), mName, Kopete::FileTransferInfo::Outgoing);
380 
381 	connect( transfer, SIGNAL(transferCanceled()), ftHandler, SLOT(cancel()) );
382 
383 	connect( ftHandler, SIGNAL(transferCancelled()), transfer, SLOT(slotCancelled()) );
384 	connect( ftHandler, SIGNAL(transferError(int,QString)), transfer, SLOT(slotError(int,QString)) );
385 	connect( ftHandler, SIGNAL(transferProcessed(uint)), transfer, SLOT(slotProcessed(uint)) );
386 	connect( ftHandler, SIGNAL(transferFinished()), transfer, SLOT(slotComplete()) );
387 	connect( ftHandler, SIGNAL(transferNextFile(QString,QString)),
388 	         transfer, SLOT(slotNextFile(QString,QString)) );
389 
390 	ftHandler->send();
391 }
392 
setAwayMessage(const QString & message)393 void OscarContact::setAwayMessage( const QString &message )
394 {
395 	kDebug(OSCAR_AIM_DEBUG) <<
396 		"Called for '" << contactId() << "', away msg='" << message << "'" << endl;
397 
398 	if ( !message.isEmpty() )
399 		setProperty( static_cast<OscarProtocol*>( protocol() )->statusMessage, filterAwayMessage( message ) );
400 	else
401 		removeProperty( static_cast<OscarProtocol*>( protocol() )->statusMessage );
402 
403 	emit statusMessageChanged( this );
404 }
405 
changeContactEncoding()406 void OscarContact::changeContactEncoding()
407 {
408 	if ( m_oesd )
409 		return;
410 
411 	OscarProtocol* p = static_cast<OscarProtocol*>( protocol() );
412 	m_oesd = new OscarEncodingSelectionDialog( Kopete::UI::Global::mainWidget(), property(p->contactEncoding).value().toInt() );
413 	connect( m_oesd, SIGNAL(closing(int)), this, SLOT(changeEncodingDialogClosed(int)) );
414 	m_oesd->show();
415 }
416 
requestAuthorization()417 void OscarContact::requestAuthorization()
418 {
419 	QString info = i18n("The user %1 requires authorization before being added to a contact list. "
420 	                    "Do you want to send an authorization request?\n\nReason for requesting authorization:",
421 	                    displayName() );
422 
423     QString reason = QInputDialog::getText( nullptr, i18n("Request Authorization"), info, QLineEdit::Normal,
424 	                                        i18n("Please authorize me so I can add you to my contact list") );
425 	if ( !reason.isNull() )
426 		mAccount->engine()->requestAuth( contactId(), reason );
427 }
428 
slotSendMsg(Kopete::Message & message,Kopete::ChatSession *)429 void OscarContact::slotSendMsg(Kopete::Message& message, Kopete::ChatSession *)
430 {
431 	if (message.plainBody().isEmpty()) // no text, do nothing
432 		return;
433 	//okay, now we need to change the message.escapedBody from real HTML to aimhtml.
434 	//looking right now for docs on that "format".
435 	//looks like everything except for alignment codes comes in the format of spans
436 
437 	//font-style:italic -> <i>
438 	//font-weight:600 -> <b> (anything > 400 should be <b>, 400 is not bold)
439 	//text-decoration:underline -> <u>
440 	//font-family: -> <font face="">
441 	//font-size:xxpt -> <font ptsize=xx>
442 
443 	QTextDocument doc;
444 	doc.setHtml( message.escapedBody() );
445 
446 	QString rtfText = QString( "<HTML><BODY dir=\"%1\">" ).arg( message.isRightToLeft() ? "rtl" : "ltr" );
447 
448 	bool hasFontTag = false;
449 	QTextCharFormat defaultCharFormat;
450 	for ( QTextBlock it = doc.begin(); it != doc.end(); it = it.next() )
451 	{
452 		QTextBlockFormat blockFormat = it.blockFormat();
453 
454 		// Plain text message has p tags without margin attributes and Qt's topMargin()
455 		// returns default margins so we will end up with line break before text.
456 		if ( message.format() != Qt::PlainText || it.blockNumber() != 0 )
457 			rtfText += brMargin( blockFormat.topMargin(), defaultCharFormat.fontPointSize() );
458 
459 		bool lastFragmentHasLineSeparator = false;
460 		for ( QTextBlock::iterator it2 = it.begin(); !(it2.atEnd()); ++it2 )
461 		{
462 			QTextFragment currentFragment = it2.fragment();
463 			if ( currentFragment.isValid() )
464 			{
465 				QTextCharFormat format = currentFragment.charFormat();
466 				if ( format.fontFamily() != defaultCharFormat.fontFamily() ||
467 				     format.foreground() != defaultCharFormat.foreground() ||
468 				     oscarFontSize(format.fontPointSize()) != oscarFontSize(defaultCharFormat.fontPointSize()) )
469 				{
470 					if ( hasFontTag )
471 					{
472 						rtfText += "</FONT>";
473 						hasFontTag = false;
474 					}
475 
476 					QString fontTag;
477 					if ( !format.fontFamily().isEmpty() )
478 						fontTag += QString( " FACE=\"%1\"" ).arg( format.fontFamily() );
479 					if ( format.fontPointSize() > 0 )
480 						fontTag += QString( " SIZE=%1" ).arg( oscarFontSize( format.fontPointSize() ) );
481 					if ( format.foreground().style() != Qt::NoBrush )
482 						fontTag += QString( " COLOR=%1" ).arg( format.foreground().color().name() );
483 					if ( format.background().style() != Qt::NoBrush )
484 						fontTag += QString( " BACK=%1" ).arg( format.background().color().name() );
485 
486 					if ( !fontTag.isEmpty() )
487 					{
488 						rtfText += QString("<FONT%1>").arg( fontTag );
489 						hasFontTag = true;
490 					}
491 				}
492 
493 				if ( format.font().bold() != defaultCharFormat.font().bold() )
494 					rtfText += ( format.font().bold() ) ? "<B>" : "</B>";
495 				if ( format.fontItalic() != defaultCharFormat.fontItalic() )
496 					rtfText += ( format.hasProperty(QTextFormat::FontItalic) ) ? "<I>" : "</I>";
497 				if ( format.fontUnderline() != defaultCharFormat.fontUnderline() )
498 					rtfText += ( format.hasProperty(QTextFormat::FontUnderline) ) ? "<U>" : "</U>";
499 
500 				QString text = currentFragment.text();
501 				lastFragmentHasLineSeparator = text.endsWith( QChar::LineSeparator );
502 				rtfText += text.toHtmlEscaped();
503 				defaultCharFormat = format;
504 			}
505 		}
506 		rtfText += brMargin( blockFormat.bottomMargin(), defaultCharFormat.fontPointSize(), !lastFragmentHasLineSeparator );
507 	}
508 
509 	rtfText.replace( QChar::LineSeparator, "<BR>" );
510 
511 	if ( rtfText.endsWith( "<BR>" ) )
512 		rtfText.chop(4);
513 
514 	if ( hasFontTag )
515 		rtfText += "</FONT>";
516 	if ( defaultCharFormat.font().bold() )
517 		rtfText += "</B>";
518 	if ( defaultCharFormat.hasProperty( QTextFormat::FontItalic ) )
519 		rtfText += "</I>";
520 	if ( defaultCharFormat.hasProperty( QTextFormat::FontUnderline ) )
521 		rtfText += "</U>";
522 
523 	rtfText += "</BODY></HTML>";
524 
525 	kDebug(OSCAR_GEN_DEBUG) << "sending: " << rtfText;
526 
527 	// TODO: Need to check for message size?
528 
529 	Oscar::Message msg;
530 	// Allow UCS2 because official AIM client doesn't sets the CAP_UTF8 anymore!
531 	bool allowUCS2 = !isOnline() || !(m_details.userClass() & Oscar::CLASS_ICQ) || m_details.hasCap( CAP_UTF8 );
532 	msg.setText( Oscar::Message::encodingForText( rtfText, allowUCS2 ), rtfText, contactCodec() );
533 
534 	msg.setId( message.id() );
535 	msg.setReceiver(mName);
536 	msg.setSender( mAccount->accountId() );
537 	msg.setTimestamp(message.timestamp());
538 	msg.setChannel(0x01);
539 
540 	mAccount->engine()->sendMessage(msg);
541 
542 	message.setState( Kopete::Message::StateSending );
543 	// Show the message we just sent in the chat window
544 	manager(Kopete::Contact::CanCreate)->appendMessage(message);
545 	manager(Kopete::Contact::CanCreate)->messageSucceeded();
546 }
547 
changeEncodingDialogClosed(int result)548 void OscarContact::changeEncodingDialogClosed( int result )
549 {
550 	if ( result == QDialog::Accepted )
551 		setEncoding( m_oesd->selectedEncoding() );
552 
553 	if ( m_oesd )
554 	{
555 		m_oesd->deleteLater();
556 		m_oesd = nullptr;
557 	}
558 }
559 
requestBuddyIcon()560 void OscarContact::requestBuddyIcon()
561 {
562 	if ( m_buddyIconDirty && m_details.buddyIconHash().size() > 0 )
563 	{
564 		account()->engine()->requestBuddyIcon( contactId(), m_details.buddyIconHash(),
565 		                                       m_details.iconType(), m_details.iconCheckSumType() );
566 	}
567 }
568 
haveIcon(const QString & user,QByteArray icon)569 void OscarContact::haveIcon( const QString& user, QByteArray icon )
570 {
571 	if ( Oscar::normalize( user ) != Oscar::normalize( contactId() ) )
572 		return;
573 
574 	kDebug(OSCAR_GEN_DEBUG) << "Updating icon for " << contactId();
575 
576 	QByteArray buddyIconHash = QCryptographicHash::hash( icon, QCryptographicHash::Md5 );
577 	if ( memcmp( buddyIconHash, m_details.buddyIconHash().data(), 16 ) == 0 )
578 	{
579 		QImage img;
580 		img.loadFromData(icon);
581 		Kopete::AvatarManager::AvatarEntry entry;
582 		entry.name = contactId();
583 		entry.category = Kopete::AvatarManager::Contact;
584 		entry.contact = this;
585 		entry.image = img;
586 		entry = Kopete::AvatarManager::self()->add(entry);
587 
588 		if (!entry.dataPath.isNull())
589 		{
590 			removeProperty( Kopete::Global::Properties::self()->photo() );
591 			setProperty( Kopete::Global::Properties::self()->photo(), entry.dataPath );
592 			setProperty( static_cast<OscarProtocol*>(protocol())->buddyIconHash, m_details.buddyIconHash() );
593 		}
594 
595 		m_buddyIconDirty = false;
596 	}
597 	else
598 	{
599 		kDebug(14153) << "Buddy icon hash does not match!";
600 		removeProperty( static_cast<OscarProtocol*>(protocol())->buddyIconHash );
601 		removeProperty( Kopete::Global::Properties::self()->photo() );
602 	}
603 }
604 
receivedStatusMessage(const QString & contact,const QString & message)605 void OscarContact::receivedStatusMessage( const QString& contact, const QString& message )
606 {
607 	if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
608 		return;
609 
610 	setAwayMessage( message );
611 }
612 
filterAwayMessage(const QString & message) const613 QString OscarContact::filterAwayMessage( const QString &message ) const
614 {
615 	QString filteredMessage = message;
616 	filteredMessage.replace(
617 	                         QRegExp(QString::fromLatin1("<[hH][tT][mM][lL].*>(.*)</[hH][tT][mM][lL]>")),
618 	                         QString::fromLatin1("\\1"));
619 	filteredMessage.replace(
620 	                         QRegExp(QString::fromLatin1("<[bB][oO][dD][yY].*>(.*)</[bB][oO][dD][yY]>")),
621 	                         QString::fromLatin1("\\1") );
622 	QRegExp fontRemover( QString::fromLatin1("<[fF][oO][nN][tT].*>(.*)</[fF][oO][nN][tT]>") );
623 	fontRemover.setMinimal(true);
624 	while ( filteredMessage.indexOf( fontRemover ) != -1 )
625 		filteredMessage.replace( fontRemover, QString::fromLatin1("\\1") );
626 	return filteredMessage;
627 }
628 
oscarFontSize(int size) const629 int OscarContact::oscarFontSize( int size ) const
630 {
631 	if ( size <= 0 )
632 		return 0;
633 	else if ( 1 <= size && size <= 9 )
634 		return 1;
635 	else if ( 10 <= size && size <= 11 )
636 		return 2;
637 	else if ( 12 <= size && size <= 13 )
638 		return 3;
639 	else if ( 14 <= size && size <= 16 )
640 		return 4;
641 	else if ( 17 <= size && size <= 22 )
642 		return 5;
643 	else if ( 23 <= size && size <= 29 )
644 		return 6;
645 	else
646 		return 7;
647 }
648 
brMargin(int margin,int fontPointSize,bool forceBr) const649 QString OscarContact::brMargin( int margin, int fontPointSize, bool forceBr ) const
650 {
651 	int brHeight = ( fontPointSize == 0 ) ? 12 : fontPointSize;
652 	int brCount = margin / brHeight;
653 
654 	if ( brCount <= 0 )
655 		return ( forceBr ) ? "<BR>" : "";
656 
657 	QString s;
658 	while ( brCount-- > 0 )
659 		s += "<BR>";
660 
661 	return s;
662 }
663 
664