summaryrefslogtreecommitdiffstats
path: root/kopete/protocols/oscar/aim/aimcontact.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kopete/protocols/oscar/aim/aimcontact.cpp')
-rw-r--r--kopete/protocols/oscar/aim/aimcontact.cpp517
1 files changed, 517 insertions, 0 deletions
diff --git a/kopete/protocols/oscar/aim/aimcontact.cpp b/kopete/protocols/oscar/aim/aimcontact.cpp
new file mode 100644
index 00000000..7e46c585
--- /dev/null
+++ b/kopete/protocols/oscar/aim/aimcontact.cpp
@@ -0,0 +1,517 @@
+/*
+ aimcontact.cpp - Oscar Protocol Plugin
+
+ Copyright (c) 2003 by Will Stephenson
+ Kopete (c) 2002-2004 by the Kopete developers <[email protected]>
+
+ *************************************************************************
+ * *
+ * 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 <time.h>
+
+#include <qimage.h>
+#include <qregexp.h>
+#include <qtimer.h>
+#include <qtextcodec.h>
+
+#include <kapplication.h>
+#include <kactionclasses.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+
+#include "kopeteaway.h"
+#include "kopetechatsession.h"
+#include "kopeteuiglobal.h"
+#include "kopetemetacontact.h"
+
+//liboscar
+#include "client.h"
+#include "oscartypes.h"
+#include "oscarutils.h"
+#include "ssimanager.h"
+
+#include "aimprotocol.h"
+#include "aimuserinfo.h"
+#include "aimcontact.h"
+#include "aimaccount.h"
+
+AIMContact::AIMContact( Kopete::Account* account, const QString& name, Kopete::MetaContact* parent,
+ const QString& icon, const Oscar::SSI& ssiItem )
+: OscarContact(account, name, parent, icon, ssiItem )
+{
+ mProtocol=static_cast<AIMProtocol *>(protocol());
+ setOnlineStatus( mProtocol->statusOffline );
+
+ m_infoDialog = 0L;
+ m_warnUserAction = 0L;
+ mUserProfile="";
+ m_haveAwayMessage = false;
+ m_mobile = false;
+ // Set the last autoresponse time to the current time yesterday
+ m_lastAutoresponseTime = QDateTime::currentDateTime().addDays(-1);
+
+ QObject::connect( mAccount->engine(), SIGNAL( receivedUserInfo( const QString&, const UserDetails& ) ),
+ this, SLOT( userInfoUpdated( const QString&, const UserDetails& ) ) );
+ QObject::connect( mAccount->engine(), SIGNAL( userIsOffline( const QString& ) ),
+ this, SLOT( userOffline( const QString& ) ) );
+ QObject::connect( mAccount->engine(), SIGNAL( receivedAwayMessage( const QString&, const QString& ) ),
+ this, SLOT( updateAwayMessage( const QString&, const QString& ) ) );
+ QObject::connect( mAccount->engine(), SIGNAL( receivedProfile( const QString&, const QString& ) ),
+ this, SLOT( updateProfile( const QString&, const QString& ) ) );
+ QObject::connect( mAccount->engine(), SIGNAL( userWarned( const QString&, Q_UINT16, Q_UINT16 ) ),
+ this, SLOT( gotWarning( const QString&, Q_UINT16, Q_UINT16 ) ) );
+ QObject::connect( mAccount->engine(), SIGNAL( haveIconForContact( const QString&, QByteArray ) ),
+ this, SLOT( haveIcon( const QString&, QByteArray ) ) );
+ QObject::connect( mAccount->engine(), SIGNAL( iconServerConnected() ),
+ this, SLOT( requestBuddyIcon() ) );
+ QObject::connect( this, SIGNAL( featuresUpdated() ), this, SLOT( updateFeatures() ) );
+}
+
+AIMContact::~AIMContact()
+{
+}
+
+bool AIMContact::isReachable()
+{
+ return true;
+}
+
+QPtrList<KAction> *AIMContact::customContextMenuActions()
+{
+
+ QPtrList<KAction> *actionCollection = new QPtrList<KAction>();
+ if ( !m_warnUserAction )
+ {
+ m_warnUserAction = new KAction( i18n( "&Warn User" ), 0, this, SLOT( warnUser() ), this, "warnAction" );
+ }
+ m_actionVisibleTo = new KToggleAction(i18n("Always &Visible To"), "", 0,
+ this, SLOT(slotVisibleTo()), this, "actionVisibleTo");
+ m_actionInvisibleTo = new KToggleAction(i18n("Always &Invisible To"), "", 0,
+ this, SLOT(slotInvisibleTo()), this, "actionInvisibleTo");
+
+ bool on = account()->isConnected();
+
+ m_warnUserAction->setEnabled( on );
+
+ m_actionVisibleTo->setEnabled(on);
+ m_actionInvisibleTo->setEnabled(on);
+
+ SSIManager* ssi = account()->engine()->ssiManager();
+ m_actionVisibleTo->setChecked( ssi->findItem( m_ssiItem.name(), ROSTER_VISIBLE ));
+ m_actionInvisibleTo->setChecked( ssi->findItem( m_ssiItem.name(), ROSTER_INVISIBLE ));
+
+ actionCollection->append( m_warnUserAction );
+
+ actionCollection->append(m_actionVisibleTo);
+ actionCollection->append(m_actionInvisibleTo);
+
+
+ return actionCollection;
+}
+
+const QString AIMContact::awayMessage()
+{
+ return property(mProtocol->awayMessage).value().toString();
+}
+
+void AIMContact::setAwayMessage(const QString &message)
+{
+ kdDebug(14152) << k_funcinfo <<
+ "Called for '" << contactId() << "', away msg='" << message << "'" << endl;
+ QString filteredMessage = message;
+ filteredMessage.replace(
+ QRegExp(QString::fromLatin1("<[hH][tT][mM][lL].*>(.*)</[hH][tT][mM][lL]>")),
+ QString::fromLatin1("\\1"));
+ filteredMessage.replace(
+ QRegExp(QString::fromLatin1("<[bB][oO][dD][yY].*>(.*)</[bB][oO][dD][yY]>")),
+ QString::fromLatin1("\\1") );
+ QRegExp fontRemover( QString::fromLatin1("<[fF][oO][nN][tT].*>(.*)</[fF][oO][nN][tT]>") );
+ fontRemover.setMinimal(true);
+ while ( filteredMessage.find( fontRemover ) != -1 )
+ filteredMessage.replace( fontRemover, QString::fromLatin1("\\1") );
+ setProperty(mProtocol->awayMessage, filteredMessage);
+}
+
+int AIMContact::warningLevel() const
+{
+ return m_warningLevel;
+}
+
+void AIMContact::updateSSIItem()
+{
+ if ( m_ssiItem.type() != 0xFFFF && m_ssiItem.waitingAuth() == false &&
+ onlineStatus() == Kopete::OnlineStatus::Unknown )
+ {
+ //make sure they're offline
+ setOnlineStatus( static_cast<AIMProtocol*>( protocol() )->statusOffline );
+ }
+}
+
+void AIMContact::slotUserInfo()
+{
+ if ( !m_infoDialog)
+ {
+ m_infoDialog = new AIMUserInfoDialog( this, static_cast<AIMAccount*>( account() ), false, Kopete::UI::Global::mainWidget(), 0 );
+ if( !m_infoDialog )
+ return;
+ connect( m_infoDialog, SIGNAL( finished() ), this, SLOT( closeUserInfoDialog() ) );
+ m_infoDialog->show();
+ if ( mAccount->isConnected() )
+ {
+ mAccount->engine()->requestAIMProfile( contactId() );
+ mAccount->engine()->requestAIMAwayMessage( contactId() );
+ }
+ }
+ else
+ m_infoDialog->raise();
+}
+
+void AIMContact::userInfoUpdated( const QString& contact, const UserDetails& details )
+{
+ if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
+ return;
+
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << contact << endl;
+
+ //if they don't have an SSI alias, make sure we use the capitalization from the
+ //server so their contact id looks all pretty.
+ QString nickname = property( Kopete::Global::Properties::self()->nickName() ).value().toString();
+ if ( nickname.isEmpty() || Oscar::normalize( nickname ) == Oscar::normalize( contact ) )
+ setNickName( contact );
+
+ ( details.userClass() & CLASS_WIRELESS ) ? m_mobile = true : m_mobile = false;
+
+ if ( ( details.userClass() & CLASS_AWAY ) == STATUS_ONLINE )
+ {
+ if ( m_mobile )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Contact: " << contact << " is mobile-online." << endl;
+ setOnlineStatus( mProtocol->statusWirelessOnline );
+ }
+ else
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Contact: " << contact << " is online." << endl;
+ setOnlineStatus( mProtocol->statusOnline ); //we're online
+ }
+ removeProperty( mProtocol->awayMessage );
+ m_haveAwayMessage = false;
+ }
+ else if ( ( details.userClass() & CLASS_AWAY ) ) // STATUS_AWAY
+ {
+ if ( m_mobile )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Contact: " << contact << " is mobile-away." << endl;
+ setOnlineStatus( mProtocol->statusWirelessOnline );
+ }
+ else
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Contact: " << contact << " is away." << endl;
+ setOnlineStatus( mProtocol->statusAway ); //we're away
+ }
+ if ( !m_haveAwayMessage ) //prevent cyclic away message requests
+ {
+ mAccount->engine()->requestAIMAwayMessage( contactId() );
+ m_haveAwayMessage = true;
+ }
+ }
+ else
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Contact: " << contact << " class " << details.userClass() << " is unhandled... defaulting to away." << endl;
+ setOnlineStatus( mProtocol->statusAway ); //we're away
+ if ( !m_haveAwayMessage ) //prevent cyclic away message requests
+ {
+ mAccount->engine()->requestAIMAwayMessage( contactId() );
+ m_haveAwayMessage = true;
+ }
+ }
+
+ if ( details.buddyIconHash().size() > 0 && details.buddyIconHash() != m_details.buddyIconHash() )
+ {
+ if ( !mAccount->engine()->hasIconConnection() )
+ mAccount->engine()->requestServerRedirect( 0x0010 );
+
+ int time = ( KApplication::random() % 10 ) * 1000;
+ kdDebug(OSCAR_ICQ_DEBUG) << k_funcinfo << "updating buddy icon in " << time/1000 << " seconds" << endl;
+ QTimer::singleShot( time, this, SLOT( requestBuddyIcon() ) );
+ }
+
+ OscarContact::userInfoUpdated( contact, details );
+}
+
+void AIMContact::userOnline( const QString& userId )
+{
+ if ( Oscar::normalize( userId ) == Oscar::normalize( contactId() ) )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Getting more contact info" << endl;
+ setOnlineStatus( mProtocol->statusOnline );
+ }
+}
+
+void AIMContact::userOffline( const QString& userId )
+{
+ if ( Oscar::normalize( userId ) == Oscar::normalize( contactId() ) )
+ {
+ setOnlineStatus( mProtocol->statusOffline );
+ removeProperty( mProtocol->awayMessage );
+ }
+}
+
+void AIMContact::updateAwayMessage( const QString& contact, const QString& message )
+{
+ if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
+ return;
+ else
+ {
+ if ( message.isEmpty() )
+ {
+ removeProperty( mProtocol->awayMessage );
+ if ( !m_mobile )
+ setOnlineStatus( mProtocol->statusOnline );
+ else
+ setOnlineStatus( mProtocol->statusWirelessOnline );
+ m_haveAwayMessage = false;
+ }
+ else
+ {
+ m_haveAwayMessage = true;
+ setAwayMessage( message );
+ if ( !m_mobile )
+ setOnlineStatus( mProtocol->statusAway );
+ else
+ setOnlineStatus( mProtocol->statusWirelessAway );
+ }
+ }
+
+ emit updatedProfile();
+}
+
+void AIMContact::updateProfile( const QString& contact, const QString& profile )
+{
+ if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
+ return;
+
+ setProperty( mProtocol->clientProfile, profile );
+ emit updatedProfile();
+}
+
+void AIMContact::gotWarning( const QString& contact, Q_UINT16 increase, Q_UINT16 newLevel )
+{
+ //somebody just got bitchslapped! :O
+ Q_UNUSED( increase );
+ if ( Oscar::normalize( contact ) == Oscar::normalize( contactId() ) )
+ m_warningLevel = newLevel;
+
+ //TODO add a KNotify event after merge to HEAD
+}
+
+void AIMContact::requestBuddyIcon()
+{
+ kdDebug(OSCAR_AIM_DEBUG) << k_funcinfo << "Updating buddy icon for " << contactId() << endl;
+ if ( m_details.buddyIconHash().size() > 0 )
+ {
+ account()->engine()->requestBuddyIcon( contactId(), m_details.buddyIconHash(),
+ m_details.iconCheckSumType() );
+ }
+}
+
+void AIMContact::haveIcon( const QString& user, QByteArray icon )
+{
+ if ( Oscar::normalize( user ) != Oscar::normalize( contactId() ) )
+ return;
+
+ kdDebug(OSCAR_AIM_DEBUG) << k_funcinfo << "Updating icon for " << contactId() << endl;
+ QImage buddyIcon( icon );
+ if ( buddyIcon.isNull() )
+ {
+ kdWarning(OSCAR_AIM_DEBUG) << k_funcinfo << "Failed to convert buddy icon to QImage" << endl;
+ return;
+ }
+
+ setProperty( Kopete::Global::Properties::self()->photo(), buddyIcon );
+}
+
+void AIMContact::closeUserInfoDialog()
+{
+ m_infoDialog->delayedDestruct();
+ m_infoDialog = 0L;
+}
+
+void AIMContact::warnUser()
+{
+ QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString();
+ QString message = i18n( "<qt>Would you like to warn %1 anonymously or with your name?<br>" \
+ "(Warning a user on AIM will result in a \"Warning Level\"" \
+ " increasing for the user you warn. Once this level has reached a" \
+ " certain point, they will not be able to sign on. Please do not abuse" \
+ " this function, it is meant for legitimate practices.)</qt>" ).arg( nick );
+
+
+ int result = KMessageBox::questionYesNoCancel( Kopete::UI::Global::mainWidget(), message,
+ i18n( "Warn User %1?" ).arg( nick ),
+ i18n( "Warn Anonymously" ), i18n( "Warn" ) );
+
+ if ( result == KMessageBox::Yes )
+ mAccount->engine()->sendWarning( contactId(), true);
+ else if ( result == KMessageBox::No )
+ mAccount->engine()->sendWarning( contactId(), false);
+}
+
+void AIMContact::slotVisibleTo()
+{
+ account()->engine()->setVisibleTo( contactId(), m_actionVisibleTo->isChecked() );
+}
+
+void AIMContact::slotInvisibleTo()
+{
+ account()->engine()->setInvisibleTo( contactId(), m_actionInvisibleTo->isChecked() );
+}
+
+void AIMContact::slotSendMsg(Kopete::Message& message, Kopete::ChatSession *)
+{
+ Oscar::Message msg;
+ QString s;
+
+ if (message.plainBody().isEmpty()) // no text, do nothing
+ return;
+ //okay, now we need to change the message.escapedBody from real HTML to aimhtml.
+ //looking right now for docs on that "format".
+ //looks like everything except for alignment codes comes in the format of spans
+
+ //font-style:italic -> <i>
+ //font-weight:600 -> <b> (anything > 400 should be <b>, 400 is not bold)
+ //text-decoration:underline -> <u>
+ //font-family: -> <font face="">
+ //font-size:xxpt -> <font ptsize=xx>
+
+ s=message.escapedBody();
+ s.replace ( QRegExp( QString::fromLatin1("<span style=\"([^\"]*)\">([^<]*)</span>")),
+ QString::fromLatin1("<style>\\1;\"\\2</style>"));
+
+ s.replace ( QRegExp( QString::fromLatin1("<style>([^\"]*)font-style:italic;([^\"]*)\"([^<]*)</style>")),
+ QString::fromLatin1("<i><style>\\1\\2\"\\3</style></i>"));
+
+ s.replace ( QRegExp( QString::fromLatin1("<style>([^\"]*)font-weight:600;([^\"]*)\"([^<]*)</style>")),
+ QString::fromLatin1("<b><style>\\1\\2\"\\3</style></b>"));
+
+ s.replace ( QRegExp( QString::fromLatin1("<style>([^\"]*)text-decoration:underline;([^\"]*)\"([^<]*)</style>")),
+ QString::fromLatin1("<u><style>\\1\\2\"\\3</style></u>"));
+
+ s.replace ( QRegExp( QString::fromLatin1("<style>([^\"]*)font-family:([^;]*);([^\"]*)\"([^<]*)</style>")),
+ QString::fromLatin1("<font face=\"\\2\"><style>\\1\\3\"\\4</style></font>"));
+
+ s.replace ( QRegExp( QString::fromLatin1("<style>([^\"]*)font-size:([^p]*)pt;([^\"]*)\"([^<]*)</style>")),
+ QString::fromLatin1("<font ptsize=\"\\2\"><style>\\1\\3\"\\4</style></font>"));
+
+ s.replace ( QRegExp( QString::fromLatin1("<style>([^\"]*)color:([^;]*);([^\"]*)\"([^<]*)</style>")),
+ QString::fromLatin1("<font color=\"\\2\"><style>\\1\\3\"\\4</style></font>"));
+
+ s.replace ( QRegExp( QString::fromLatin1("<style>([^\"]*)\"([^<]*)</style>")),
+ QString::fromLatin1("\\2"));
+
+ //okay now change the <font ptsize="xx"> to <font size="xx">
+
+ //0-9 are size 1
+ s.replace ( QRegExp ( QString::fromLatin1("<font ptsize=\"\\d\">")),
+ QString::fromLatin1("<font size=\"1\">"));
+ //10-11 are size 2
+ s.replace ( QRegExp ( QString::fromLatin1("<font ptsize=\"1[01]\">")),
+ QString::fromLatin1("<font size=\"2\">"));
+ //12-13 are size 3
+ s.replace ( QRegExp ( QString::fromLatin1("<font ptsize=\"1[23]\">")),
+ QString::fromLatin1("<font size=\"3\">"));
+ //14-16 are size 4
+ s.replace ( QRegExp ( QString::fromLatin1("<font ptsize=\"1[456]\">")),
+ QString::fromLatin1("<font size=\"4\">"));
+ //17-22 are size 5
+ s.replace ( QRegExp ( QString::fromLatin1("<font ptsize=\"(?:1[789]|2[012])\">")),
+ QString::fromLatin1("<font size=\"5\">"));
+ //23-29 are size 6
+ s.replace ( QRegExp ( QString::fromLatin1("<font ptsize=\"2[3456789]\">")),QString::fromLatin1("<font size=\"6\">"));
+ //30- (and any I missed) are size 7
+ s.replace ( QRegExp ( QString::fromLatin1("<font ptsize=\"[^\"]*\">")),QString::fromLatin1("<font size=\"7\">"));
+
+ s.replace ( QRegExp ( QString::fromLatin1("<br[ /]*>")), QString::fromLatin1("<br>") );
+
+ // strip left over line break
+ s.remove( QRegExp( QString::fromLatin1( "<br>$" ) ) );
+
+ kdDebug(14190) << k_funcinfo << "sending "
+ << s << endl;
+
+ // XXX Need to check for message size?
+
+ if ( m_details.hasCap( CAP_UTF8 ) )
+ msg.setText( Oscar::Message::UCS2, s );
+ else
+ msg.setText( Oscar::Message::UserDefined, s, contactCodec() );
+
+ msg.setReceiver(mName);
+ msg.setTimestamp(message.timestamp());
+ msg.setType(0x01);
+
+ mAccount->engine()->sendMessage(msg);
+
+ // Show the message we just sent in the chat window
+ manager(Kopete::Contact::CanCreate)->appendMessage(message);
+ manager(Kopete::Contact::CanCreate)->messageSucceeded();
+}
+
+void AIMContact::updateFeatures()
+{
+ setProperty( static_cast<AIMProtocol*>(protocol())->clientFeatures, m_clientFeatures );
+}
+
+void AIMContact::sendAutoResponse(Kopete::Message& msg)
+{
+ // The target time is 2 minutes later than the last message
+ int delta = m_lastAutoresponseTime.secsTo( QDateTime::currentDateTime() );
+ kdDebug(14152) << k_funcinfo << "Last autoresponse time: " << m_lastAutoresponseTime << endl;
+ kdDebug(14152) << k_funcinfo << "Current time: " << QDateTime::currentDateTime() << endl;
+ kdDebug(14152) << k_funcinfo << "Difference: " << delta << endl;
+ // Check to see if we're past that time
+ if(delta > 120)
+ {
+ kdDebug(14152) << k_funcinfo << "Sending auto response" << endl;
+
+ // This code was yoinked straight from OscarContact::slotSendMsg()
+ // If only that slot wasn't private, but I'm not gonna change it right now.
+ Oscar::Message message;
+
+ if ( m_details.hasCap( CAP_UTF8 ) )
+ {
+ message.setText( Oscar::Message::UCS2, msg.plainBody() );
+ }
+ else
+ {
+ QTextCodec* codec = contactCodec();
+ message.setText( Oscar::Message::UserDefined, msg.plainBody(), codec );
+ }
+
+ message.setTimestamp( msg.timestamp() );
+ message.setSender( mAccount->accountId() );
+ message.setReceiver( mName );
+ message.setType( 0x01 );
+
+ // isAuto defaults to false
+ mAccount->engine()->sendMessage( message, true);
+ kdDebug(14152) << k_funcinfo << "Sent auto response" << endl;
+ manager(Kopete::Contact::CanCreate)->appendMessage(msg);
+ manager(Kopete::Contact::CanCreate)->messageSucceeded();
+ // Update the last autoresponse time
+ m_lastAutoresponseTime = QDateTime::currentDateTime();
+ }
+ else
+ {
+ kdDebug(14152) << k_funcinfo << "Not enough time since last autoresponse, NOT sending" << endl;
+ }
+}
+#include "aimcontact.moc"
+//kate: tab-width 4; indent-mode csands;