PageRenderTime 409ms CodeModel.GetById 55ms app.highlight 286ms RepoModel.GetById 56ms app.codeStats 0ms

/src/sip/twitter/twitter.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 1071 lines | 885 code | 167 blank | 19 comment | 149 complexity | 17d704719ce50f7dea2cda5d9f2fb07f MD5 | raw file
   1/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
   2 *
   3 *   Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
   4 *
   5 *   Tomahawk is free software: you can redistribute it and/or modify
   6 *   it under the terms of the GNU General Public License as published by
   7 *   the Free Software Foundation, either version 3 of the License, or
   8 *   (at your option) any later version.
   9 *
  10 *   Tomahawk is distributed in the hope that it will be useful,
  11 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 *   GNU General Public License for more details.
  14 *
  15 *   You should have received a copy of the GNU General Public License
  16 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  17 */
  18
  19#include "twitter.h"
  20
  21#include "twitterconfigwidget.h"
  22
  23#include <QtPlugin>
  24#include <QDateTime>
  25#include <QRegExp>
  26#include <QNetworkAccessManager>
  27#include <QNetworkRequest>
  28#include <QNetworkReply>
  29#include <QStringList>
  30
  31#include <QTweetLib/qtweetaccountverifycredentials.h>
  32#include <QTweetLib/qtweetuser.h>
  33#include <QTweetLib/qtweetstatus.h>
  34#include <QTweetLib/qtweetusershow.h>
  35
  36#include <utils/tomahawkutils.h>
  37#include <tomahawksettings.h>
  38#include <database/database.h>
  39#include <network/servent.h>
  40
  41#include "utils/logger.h"
  42
  43static QString s_gotTomahawkRegex = QString( "^(@[a-zA-Z0-9]+ )?(Got Tomahawk\\?) (\\{[a-fA-F0-9\\-]+\\}) (.*)$" );
  44
  45
  46SipPlugin*
  47TwitterFactory::createPlugin( const QString& pluginId )
  48{
  49    return new TwitterPlugin( pluginId.isEmpty() ? generateId() : pluginId );
  50}
  51
  52QIcon TwitterFactory::icon() const
  53{
  54    return QIcon( ":/twitter-icon.png" );
  55}
  56
  57
  58TwitterPlugin::TwitterPlugin( const QString& pluginId )
  59    : SipPlugin( pluginId )
  60    , m_isAuthed( false )
  61    , m_checkTimer( this )
  62    , m_connectTimer( this )
  63    , m_dmPollTimer( this )
  64    , m_cachedFriendsSinceId( 0 )
  65    , m_cachedMentionsSinceId( 0 )
  66    , m_cachedDirectMessagesSinceId( 0 )
  67    , m_cachedPeers()
  68    , m_keyCache()
  69    , m_state( Disconnected )
  70{
  71    qDebug() << Q_FUNC_INFO;
  72
  73    if ( Database::instance()->dbid() != twitterSavedDbid() )
  74        setTwitterCachedPeers( QVariantHash() );
  75
  76    setTwitterSavedDbid( Database::instance()->dbid() );
  77
  78    m_checkTimer.setInterval( 180000 );
  79    m_checkTimer.setSingleShot( false );
  80    connect( &m_checkTimer, SIGNAL( timeout() ), SLOT( checkTimerFired() ) );
  81
  82    m_dmPollTimer.setInterval( 60000 );
  83    m_dmPollTimer.setSingleShot( false );
  84    connect( &m_dmPollTimer, SIGNAL( timeout() ), SLOT( pollDirectMessages() ) );
  85
  86    m_connectTimer.setInterval( 180000 );
  87    m_connectTimer.setSingleShot( false );
  88    connect( &m_connectTimer, SIGNAL( timeout() ), SLOT( connectTimerFired() ) );
  89
  90    m_configWidget = QWeakPointer< TwitterConfigWidget >( new TwitterConfigWidget( this, 0 ) );
  91    connect( m_configWidget.data(), SIGNAL( twitterAuthed( bool ) ), SLOT( configDialogAuthedSignalSlot( bool ) ) );
  92}
  93
  94
  95void
  96TwitterPlugin::configDialogAuthedSignalSlot( bool authed )
  97{
  98
  99    if ( !authed )
 100    {
 101        if( m_isAuthed ) {
 102            m_state = Disconnected;
 103            emit stateChanged( m_state );
 104        }
 105
 106        setTwitterScreenName( QString() );
 107        setTwitterOAuthToken( QString() );
 108        setTwitterOAuthTokenSecret( QString() );
 109    }
 110
 111    m_isAuthed = authed;
 112}
 113
 114bool
 115TwitterPlugin::isValid() const
 116{
 117    return m_isAuthed;
 118}
 119
 120const QString
 121TwitterPlugin::name() const
 122{
 123    return QString( MYNAME );
 124}
 125
 126const QString
 127TwitterPlugin::friendlyName() const
 128{
 129    return tr("Twitter");
 130}
 131
 132const QString
 133TwitterPlugin::accountName() const
 134{
 135    if( twitterScreenName().isEmpty() )
 136        return friendlyName();
 137    else
 138        return twitterScreenName();
 139}
 140
 141QIcon
 142TwitterPlugin::icon() const
 143{
 144    return QIcon( ":/twitter-icon.png" );
 145}
 146
 147
 148SipPlugin::ConnectionState
 149TwitterPlugin::connectionState() const
 150{
 151    return m_state;
 152}
 153
 154
 155QWidget* TwitterPlugin::configWidget()
 156{
 157    return m_configWidget.data();
 158}
 159
 160bool
 161TwitterPlugin::connectPlugin( bool startup )
 162{
 163    Q_UNUSED( startup );
 164    qDebug() << Q_FUNC_INFO;
 165
 166    m_cachedPeers = twitterCachedPeers();
 167    QStringList peerList = m_cachedPeers.keys();
 168    qStableSort( peerList.begin(), peerList.end() );
 169
 170    registerOffers( peerList );
 171
 172    if ( twitterOAuthToken().isEmpty() || twitterOAuthTokenSecret().isEmpty() )
 173    {
 174        qDebug() << "TwitterPlugin has empty Twitter credentials; not connecting";
 175        return m_cachedPeers.isEmpty();
 176    }
 177
 178    if ( refreshTwitterAuth() )
 179    {
 180      QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this );
 181      connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) );
 182      credVerifier->verify();
 183
 184      m_state = Connecting;
 185      emit stateChanged( m_state );
 186    }
 187
 188    return true;
 189}
 190
 191bool
 192TwitterPlugin::refreshTwitterAuth()
 193{
 194    qDebug() << Q_FUNC_INFO << " begin";
 195    if( !m_twitterAuth.isNull() )
 196        delete m_twitterAuth.data();
 197
 198    Q_ASSERT( TomahawkUtils::nam() != 0 );
 199    qDebug() << Q_FUNC_INFO << " with nam " << TomahawkUtils::nam();
 200    m_twitterAuth = QWeakPointer<TomahawkOAuthTwitter>( new TomahawkOAuthTwitter( TomahawkUtils::nam(), this ) );
 201
 202    if( m_twitterAuth.isNull() )
 203      return false;
 204
 205    m_twitterAuth.data()->setOAuthToken( twitterOAuthToken().toLatin1() );
 206    m_twitterAuth.data()->setOAuthTokenSecret( twitterOAuthTokenSecret().toLatin1() );
 207
 208    return true;
 209}
 210
 211void
 212TwitterPlugin::disconnectPlugin()
 213{
 214    qDebug() << Q_FUNC_INFO;
 215    m_checkTimer.stop();
 216    m_connectTimer.stop();
 217    m_dmPollTimer.stop();
 218    if( !m_friendsTimeline.isNull() )
 219        delete m_friendsTimeline.data();
 220    if( !m_mentions.isNull() )
 221        delete m_mentions.data();
 222    if( !m_directMessages.isNull() )
 223        delete m_directMessages.data();
 224    if( !m_directMessageNew.isNull() )
 225        delete m_directMessageNew.data();
 226    if( !m_directMessageDestroy.isNull() )
 227        delete m_directMessageDestroy.data();
 228    if( !m_twitterAuth.isNull() )
 229        delete m_twitterAuth.data();
 230
 231    syncConfig();
 232    m_cachedPeers.empty();
 233    m_state = Disconnected;
 234    emit stateChanged( m_state );
 235}
 236
 237void
 238TwitterPlugin::connectAuthVerifyReply( const QTweetUser &user )
 239{
 240    if ( user.id() == 0 )
 241    {
 242        qDebug() << "TwitterPlugin could not authenticate to Twitter";
 243        m_isAuthed = false;
 244        m_state = Disconnected;
 245        m_connectTimer.stop();
 246        m_checkTimer.stop();
 247        m_dmPollTimer.stop();
 248        emit stateChanged( m_state );
 249    }
 250    else
 251    {
 252        qDebug() << "TwitterPlugin successfully authenticated to Twitter as user " << user.screenName();
 253        m_isAuthed = true;
 254        if ( !m_twitterAuth.isNull() )
 255        {
 256            setTwitterScreenName( user.screenName() );
 257            m_friendsTimeline = QWeakPointer<QTweetFriendsTimeline>( new QTweetFriendsTimeline( m_twitterAuth.data(), this ) );
 258            m_mentions = QWeakPointer<QTweetMentions>( new QTweetMentions( m_twitterAuth.data(), this ) );
 259            m_directMessages = QWeakPointer<QTweetDirectMessages>( new QTweetDirectMessages( m_twitterAuth.data(), this ) );
 260            m_directMessageNew = QWeakPointer<QTweetDirectMessageNew>( new QTweetDirectMessageNew( m_twitterAuth.data(), this ) );
 261            m_directMessageDestroy = QWeakPointer<QTweetDirectMessageDestroy>( new QTweetDirectMessageDestroy( m_twitterAuth.data(), this ) );
 262            connect( m_friendsTimeline.data(), SIGNAL( parsedStatuses(const QList< QTweetStatus > &) ), SLOT( friendsTimelineStatuses(const QList<QTweetStatus> &) ) );
 263            connect( m_mentions.data(), SIGNAL( parsedStatuses(const QList< QTweetStatus > &) ), SLOT( mentionsStatuses(const QList<QTweetStatus> &) ) );
 264            connect( m_directMessages.data(), SIGNAL( parsedDirectMessages(const QList<QTweetDMStatus> &)), SLOT( directMessages(const QList<QTweetDMStatus> &) ) );
 265            connect( m_directMessageNew.data(), SIGNAL( parsedDirectMessage(const QTweetDMStatus &)), SLOT( directMessagePosted(const QTweetDMStatus &) ) );
 266            connect( m_directMessageNew.data(), SIGNAL( error(QTweetNetBase::ErrorCode, const QString &) ), SLOT( directMessagePostError(QTweetNetBase::ErrorCode, const QString &) ) );
 267            connect( m_directMessageDestroy.data(), SIGNAL( parsedDirectMessage(const QTweetDMStatus &) ), SLOT( directMessageDestroyed(const QTweetDMStatus &) ) );
 268            m_state = Connected;
 269            emit stateChanged( m_state );
 270            m_connectTimer.start();
 271            m_checkTimer.start();
 272            m_dmPollTimer.start();
 273            QMetaObject::invokeMethod( this, "checkTimerFired", Qt::AutoConnection );
 274            QTimer::singleShot( 20000, this, SLOT( connectTimerFired() ) );
 275        }
 276        else
 277        {
 278            if ( refreshTwitterAuth() )
 279            {
 280                QTweetAccountVerifyCredentials *credVerifier = new QTweetAccountVerifyCredentials( m_twitterAuth.data(), this );
 281                connect( credVerifier, SIGNAL( parsedUser(const QTweetUser &) ), SLOT( connectAuthVerifyReply(const QTweetUser &) ) );
 282                credVerifier->verify();
 283            }
 284            else
 285            {
 286                qDebug() << "TwitterPlugin auth pointer was null!";
 287                m_isAuthed = false;
 288                m_state = Disconnected;
 289                m_connectTimer.stop();
 290                m_checkTimer.stop();
 291                m_dmPollTimer.stop();
 292                emit stateChanged( m_state );
 293            }
 294        }
 295    }
 296}
 297
 298void
 299TwitterPlugin::deletePlugin()
 300{
 301    TomahawkSettings::instance()->remove( pluginId() );
 302}
 303
 304void
 305TwitterPlugin::checkTimerFired()
 306{
 307    if ( !isValid() || m_twitterAuth.isNull() )
 308        return;
 309
 310    if ( m_cachedFriendsSinceId == 0 )
 311        m_cachedFriendsSinceId = twitterCachedFriendsSinceId();
 312
 313    qDebug() << "TwitterPlugin looking at friends timeline since id " << m_cachedFriendsSinceId;
 314
 315    if ( !m_friendsTimeline.isNull() )
 316        m_friendsTimeline.data()->fetch( m_cachedFriendsSinceId, 0, 800 );
 317
 318    if ( m_cachedMentionsSinceId == 0 )
 319        m_cachedMentionsSinceId = twitterCachedMentionsSinceId();
 320
 321    qDebug() << "TwitterPlugin looking at mentions timeline since id " << m_cachedMentionsSinceId;
 322
 323    if ( !m_mentions.isNull() )
 324        m_mentions.data()->fetch( m_cachedMentionsSinceId, 0, 800 );
 325}
 326
 327
 328void
 329TwitterPlugin::registerOffers( const QStringList &peerList )
 330{
 331    foreach( QString screenName, peerList )
 332    {
 333        QVariantHash peerData = m_cachedPeers[screenName].toHash();
 334
 335        if ( peerData.contains( "onod" ) && peerData["onod"] != Database::instance()->dbid() )
 336        {
 337            m_cachedPeers.remove( screenName );
 338            syncConfig();
 339        }
 340
 341        if ( Servent::instance()->connectedToSession( peerData["node"].toString() ) )
 342        {
 343            peerData["lastseen"] = QDateTime::currentMSecsSinceEpoch();
 344            m_cachedPeers[screenName] = peerData;
 345            syncConfig();
 346            qDebug() << Q_FUNC_INFO << " already connected";
 347            continue;
 348        }
 349        else if ( QDateTime::currentMSecsSinceEpoch() - peerData["lastseen"].toLongLong() > 1209600000 ) // 2 weeks
 350        {
 351            qDebug() << Q_FUNC_INFO << " aging peer " << screenName << " out of cache";
 352            m_cachedPeers.remove( screenName );
 353            syncConfig();
 354            m_cachedAvatars.remove( screenName );
 355            continue;
 356        }
 357
 358        if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) )
 359        {
 360            qDebug() << "TwitterPlugin does not have host, port and/or pkey values for " << screenName << " (this is usually *not* a bug or problem but a normal part of the process)";
 361            continue;
 362        }
 363
 364        QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), Q_ARG( QVariantHash, peerData ) );
 365    }
 366}
 367
 368
 369void
 370TwitterPlugin::connectTimerFired()
 371{
 372    qDebug() << Q_FUNC_INFO << " beginning";
 373    if ( !isValid() || m_cachedPeers.isEmpty() || m_twitterAuth.isNull() )
 374    {
 375        if ( !isValid() )
 376            qDebug() << Q_FUNC_INFO << " is not valid";
 377        if ( m_cachedPeers.isEmpty() )
 378            qDebug() << Q_FUNC_INFO << " has empty cached peers";
 379        if ( m_twitterAuth.isNull() )
 380            qDebug() << Q_FUNC_INFO << " has null twitterAuth";
 381        return;
 382    }
 383
 384    qDebug() << Q_FUNC_INFO << " continuing";
 385    QString myScreenName = twitterScreenName();
 386    QStringList peerList = m_cachedPeers.keys();
 387    qStableSort( peerList.begin(), peerList.end() );
 388    registerOffers( peerList );
 389}
 390
 391void
 392TwitterPlugin::parseGotTomahawk( const QRegExp &regex, const QString &screenName, const QString &text )
 393{
 394    QString myScreenName = twitterScreenName();
 395    qDebug() << "TwitterPlugin found an exact matching Got Tomahawk? mention or direct message from user " << screenName << ", now parsing";
 396    regex.exactMatch( text );
 397    if ( text.startsWith( '@' ) && regex.captureCount() >= 2 && regex.cap( 1 ) != QString( '@' + myScreenName ) )
 398    {
 399        qDebug() << "TwitterPlugin skipping mention because it's directed @someone that isn't us";
 400        return;
 401    }
 402
 403    QString node;
 404    for ( int i = 0; i < regex.captureCount(); ++i )
 405    {
 406        if ( regex.cap( i ) == QString( "Got Tomahawk?" ) )
 407        {
 408            QString nodeCap = regex.cap( i + 1 );
 409            nodeCap.chop( 1 );
 410            node = nodeCap.mid( 1 );
 411        }
 412    }
 413    if ( node.isEmpty() )
 414    {
 415        qDebug() << "TwitterPlugin could not parse node out of the tweet";
 416        return;
 417    }
 418    else
 419        qDebug() << "TwitterPlugin parsed node " << node << " out of the tweet";
 420
 421    if ( node == Database::instance()->dbid() )
 422    {
 423        qDebug() << "My dbid found; ignoring";
 424        return;
 425    }
 426
 427    QVariantHash peerData;
 428    if( m_cachedPeers.contains( screenName ) )
 429    {
 430        peerData = m_cachedPeers[screenName].toHash();
 431        //force a re-send of info but no need to re-register
 432        peerData["resend"] = QVariant::fromValue< bool >( true );
 433        if ( peerData["node"].toString() != node )
 434            peerData["rekey"] = QVariant::fromValue< bool >( true );
 435    }
 436    peerData["node"] = QVariant::fromValue< QString >( node );
 437    QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), Q_ARG( QVariantHash, peerData ) );
 438}
 439
 440void
 441TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses )
 442{
 443    qDebug() << Q_FUNC_INFO;
 444    QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 );
 445
 446    QHash< QString, QTweetStatus > latestHash;
 447    foreach ( QTweetStatus status, statuses )
 448    {
 449        if ( !regex.exactMatch( status.text() ) )
 450            continue;
 451
 452        if ( !latestHash.contains( status.user().screenName() ) )
 453            latestHash[status.user().screenName()] = status;
 454        else
 455        {
 456            if ( status.id() > latestHash[status.user().screenName()].id() )
 457                latestHash[status.user().screenName()] = status;
 458        }
 459    }
 460
 461    foreach( QTweetStatus status, latestHash.values() )
 462    {
 463        if ( status.id() > m_cachedFriendsSinceId )
 464            m_cachedFriendsSinceId = status.id();
 465
 466        qDebug() << "TwitterPlugin checking mention from " << status.user().screenName() << " with content " << status.text();
 467        parseGotTomahawk( regex, status.user().screenName(), status.text() );
 468    }
 469
 470    setTwitterCachedFriendsSinceId( m_cachedFriendsSinceId );
 471}
 472
 473void
 474TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses )
 475{
 476    qDebug() << Q_FUNC_INFO;
 477    QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 );
 478
 479    QHash< QString, QTweetStatus > latestHash;
 480    foreach ( QTweetStatus status, statuses )
 481    {
 482        if ( !regex.exactMatch( status.text() ) )
 483            continue;
 484
 485        if ( !latestHash.contains( status.user().screenName() ) )
 486            latestHash[status.user().screenName()] = status;
 487        else
 488        {
 489            if ( status.id() > latestHash[status.user().screenName()].id() )
 490                latestHash[status.user().screenName()] = status;
 491        }
 492    }
 493
 494    foreach( QTweetStatus status, latestHash.values() )
 495    {
 496        if ( status.id() > m_cachedMentionsSinceId )
 497            m_cachedMentionsSinceId = status.id();
 498
 499        qDebug() << "TwitterPlugin checking mention from " << status.user().screenName() << " with content " << status.text();
 500        parseGotTomahawk( regex, status.user().screenName(), status.text() );
 501    }
 502
 503    setTwitterCachedMentionsSinceId( m_cachedMentionsSinceId );
 504}
 505
 506void
 507TwitterPlugin::pollDirectMessages()
 508{
 509    if ( !isValid() )
 510        return;
 511
 512    if ( m_cachedDirectMessagesSinceId == 0 )
 513            m_cachedDirectMessagesSinceId = twitterCachedDirectMessagesSinceId();
 514
 515    qDebug() << "TwitterPlugin looking for direct messages since id " << m_cachedDirectMessagesSinceId;
 516
 517    if ( !m_directMessages.isNull() )
 518        m_directMessages.data()->fetch( m_cachedDirectMessagesSinceId, 0, 800 );
 519}
 520
 521void
 522TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages )
 523{
 524    qDebug() << Q_FUNC_INFO;
 525
 526    QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 );
 527    QString myScreenName = twitterScreenName();
 528
 529    QHash< QString, QTweetDMStatus > latestHash;
 530    foreach ( QTweetDMStatus status, messages )
 531    {
 532        if ( !regex.exactMatch( status.text() ) )
 533        {
 534            QStringList splitList = status.text().split(':');
 535            if ( splitList.length() != 5 )
 536                continue;
 537            if ( splitList[0] != "TOMAHAWKPEER" )
 538                continue;
 539            if ( !splitList[1].startsWith( "Host=" ) || !splitList[2].startsWith( "Port=" ) || !splitList[3].startsWith( "Node=" ) || !splitList[4].startsWith( "PKey=" ) )
 540                continue;
 541            int port = splitList[2].mid( 5 ).toInt();
 542            if ( port == 0 )
 543                continue;
 544        }
 545
 546        if ( !latestHash.contains( status.senderScreenName() ) )
 547            latestHash[status.senderScreenName()] = status;
 548        else
 549        {
 550            if ( status.id() > latestHash[status.senderScreenName()].id() )
 551                latestHash[status.senderScreenName()] = status;
 552        }
 553    }
 554
 555    foreach( QTweetDMStatus status, latestHash.values() )
 556    {
 557        qDebug() << "TwitterPlugin checking direct message from " << status.senderScreenName() << " with content " << status.text();
 558        if ( status.id() > m_cachedDirectMessagesSinceId )
 559            m_cachedDirectMessagesSinceId = status.id();
 560
 561        if ( regex.exactMatch( status.text() ) )
 562            parseGotTomahawk( regex, status.sender().screenName(), status.text() );
 563        else
 564        {
 565            QStringList splitList = status.text().split(':');
 566            qDebug() << "TwitterPlugin found " << splitList.length() << " parts to the message; the parts are:";
 567            foreach( QString part, splitList )
 568                qDebug() << part;
 569            //validity is checked above
 570            int port = splitList[2].mid( 5 ).toInt();
 571            QString host = splitList[1].mid( 5 );
 572            QString node = splitList[3].mid( 5 );
 573            QString pkey = splitList[4].mid( 5 );
 574            QStringList splitNode = node.split('*');
 575            if ( splitNode.length() != 2 )
 576            {
 577                qDebug() << "Old-style node info found, ignoring";
 578                continue;
 579            }
 580            qDebug() << "TwitterPlugin found a peerstart message from " << status.senderScreenName() << " with host " << host << " and port " << port << " and pkey " << pkey << " and node " << splitNode[0] << " destined for node " << splitNode[1];
 581
 582
 583            QVariantHash peerData = ( m_cachedPeers.contains( status.senderScreenName() ) ) ?
 584                                                        m_cachedPeers[status.senderScreenName()].toHash() :
 585                                                        QVariantHash();
 586
 587            peerData["host"] = QVariant::fromValue< QString >( host );
 588            peerData["port"] = QVariant::fromValue< int >( port );
 589            peerData["pkey"] = QVariant::fromValue< QString >( pkey );
 590            peerData["node"] = QVariant::fromValue< QString >( splitNode[0] );
 591            peerData["dirty"] = QVariant::fromValue< bool >( true );
 592
 593            QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.senderScreenName() ), Q_ARG( QVariantHash, peerData ) );
 594
 595            if ( Database::instance()->dbid().startsWith( splitNode[1] ) )
 596            {
 597                qDebug() << "TwitterPlugin found message destined for this node; destroying it";
 598                if ( !m_directMessageDestroy.isNull() )
 599                    m_directMessageDestroy.data()->destroyMessage( status.id() );
 600            }
 601        }
 602    }
 603
 604    setTwitterCachedDirectMessagesSinceId( m_cachedDirectMessagesSinceId );
 605}
 606
 607void
 608TwitterPlugin::registerOffer( const QString &screenName, const QVariantHash &peerData )
 609{
 610    qDebug() << Q_FUNC_INFO;
 611
 612    bool peersChanged = false;
 613    bool needToSend = false;
 614    bool needToAddToCache = false;
 615
 616    QString friendlyName = QString( '@' + screenName );
 617
 618    if ( !m_cachedAvatars.contains( screenName ) )
 619        QMetaObject::invokeMethod( this, "fetchAvatar", Q_ARG( QString, screenName ) );
 620
 621    QVariantHash _peerData( peerData );
 622
 623    if ( _peerData.contains( "dirty" ) )
 624    {
 625        peersChanged = true;
 626        _peerData.remove( "dirty" );
 627    }
 628
 629    if ( _peerData.contains( "resend" ) )
 630    {
 631        needToSend = true;
 632        peersChanged = true;
 633        _peerData.remove( "resend" );
 634    }
 635
 636    if ( !_peerData.contains( "okey" ) ||
 637         !_peerData.contains( "onod" ) ||
 638         ( _peerData.contains( "onod" ) && _peerData["onod"] != Database::instance()->dbid() ) )
 639    {
 640        QString okey = QUuid::createUuid().toString().split( '-' ).last();
 641        okey.chop( 1 );
 642        _peerData["okey"] = QVariant::fromValue< QString >( okey );
 643        _peerData["onod"] = QVariant::fromValue< QString >( Database::instance()->dbid() );
 644        peersChanged = true;
 645        needToAddToCache = true;
 646        needToSend = true;
 647    }
 648
 649    if ( _peerData.contains( "rekey" ) || !m_keyCache.contains( _peerData["okey"].toString() ) )
 650    {
 651        _peerData.remove( "rekey" );
 652        needToAddToCache = true;
 653    }
 654
 655    if ( !_peerData.contains( "ohst" ) || !_peerData.contains( "oprt" ) ||
 656            _peerData["ohst"].toString() != Servent::instance()->externalAddress() ||
 657            _peerData["oprt"].toInt() != Servent::instance()->externalPort()
 658        )
 659        needToSend = true;
 660
 661    if( needToAddToCache && _peerData.contains( "node" ) )
 662    {
 663        qDebug() << "TwitterPlugin registering offer to " << friendlyName << " with node " << _peerData["node"].toString() << " and offeredkey " << _peerData["okey"].toString();
 664        m_keyCache << Servent::instance()->createConnectionKey( friendlyName, _peerData["node"].toString(), _peerData["okey"].toString(), false );
 665    }
 666
 667    if( needToSend && _peerData.contains( "node") )
 668    {
 669        qDebug() << "TwitterPlugin needs to send and has node";
 670        _peerData["ohst"] = QVariant::fromValue< QString >( Servent::instance()->externalAddress() );
 671        _peerData["oprt"] = QVariant::fromValue< int >( Servent::instance()->externalPort() );
 672        peersChanged = true;
 673        if( !Servent::instance()->externalAddress().isEmpty() && !Servent::instance()->externalPort() == 0 )
 674            QMetaObject::invokeMethod( this, "sendOffer", Q_ARG( QString, screenName ), Q_ARG( QVariantHash, _peerData ) );
 675        else
 676            qDebug() << "TwitterPlugin did not send offer because external address is " << Servent::instance()->externalAddress() << " and external port is " << Servent::instance()->externalPort();
 677    }
 678
 679    if ( peersChanged )
 680    {
 681        _peerData["lastseen"] = QString::number( QDateTime::currentMSecsSinceEpoch() );
 682        m_cachedPeers[screenName] = QVariant::fromValue< QVariantHash >( _peerData );
 683        syncConfig();
 684    }
 685
 686    if ( m_state == Connected && _peerData.contains( "host" ) && _peerData.contains( "port" ) && _peerData.contains( "pkey" ) )
 687        QMetaObject::invokeMethod( this, "makeConnection", Q_ARG( QString, screenName ), Q_ARG( QVariantHash, _peerData ) );
 688
 689}
 690
 691void
 692TwitterPlugin::sendOffer( const QString &screenName, const QVariantHash &peerData )
 693{
 694    qDebug() << Q_FUNC_INFO;
 695    QString offerString = QString( "TOMAHAWKPEER:Host=%1:Port=%2:Node=%3*%4:PKey=%5" ).arg( peerData["ohst"].toString() )
 696                                                                                      .arg( peerData["oprt"].toString() )
 697                                                                                      .arg( Database::instance()->dbid() )
 698                                                                                      .arg( peerData["node"].toString().left( 8 ) )
 699                                                                                      .arg( peerData["okey"].toString() );
 700    qDebug() << "TwitterPlugin sending message to " << screenName << ": " << offerString;
 701    if( !m_directMessageNew.isNull() )
 702        m_directMessageNew.data()->post( screenName, offerString );
 703}
 704
 705void
 706TwitterPlugin::makeConnection( const QString &screenName, const QVariantHash &peerData )
 707{
 708    qDebug() << Q_FUNC_INFO;
 709    if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) ||
 710         peerData["host"].toString().isEmpty() || peerData["port"].toString().isEmpty() || peerData["pkey"].toString().isEmpty() || peerData["node"].toString().isEmpty() )
 711    {
 712        qDebug() << "TwitterPlugin could not find host and/or port and/or pkey and/or node for peer " << screenName;
 713        return;
 714    }
 715
 716    if ( peerData["host"].toString() == Servent::instance()->externalAddress() &&
 717         peerData["port"].toInt() == Servent::instance()->externalPort() )
 718    {
 719        qDebug() << "TwitterPlugin asked to make connection to our own host and port, ignoring " << screenName;
 720        return;
 721    }
 722
 723    QString friendlyName = QString( '@' + screenName );
 724    if ( !Servent::instance()->connectedToSession( peerData["node"].toString() ) )
 725        Servent::instance()->connectToPeer( peerData["host"].toString(),
 726                                            peerData["port"].toString().toInt(),
 727                                            peerData["pkey"].toString(),
 728                                            friendlyName,
 729                                            peerData["node"].toString() );
 730}
 731
 732void
 733TwitterPlugin::directMessagePosted( const QTweetDMStatus& message )
 734{
 735    qDebug() << Q_FUNC_INFO;
 736    qDebug() << "TwitterPlugin sent message to " << message.recipientScreenName() << " containing: " << message.text();
 737
 738}
 739
 740void
 741TwitterPlugin::directMessagePostError( QTweetNetBase::ErrorCode errorCode, const QString &message )
 742{
 743    Q_UNUSED( errorCode );
 744    Q_UNUSED( message );
 745    qDebug() << Q_FUNC_INFO;
 746    qDebug() << "TwitterPlugin received an error posting direct message: " << m_directMessageNew.data()->lastErrorMessage();
 747}
 748
 749void
 750TwitterPlugin::directMessageDestroyed( const QTweetDMStatus& message )
 751{
 752    qDebug() << Q_FUNC_INFO;
 753    qDebug() << "TwitterPlugin destroyed message " << message.text();
 754}
 755
 756void
 757TwitterPlugin::fetchAvatar( const QString& screenName )
 758{
 759    qDebug() << Q_FUNC_INFO;
 760    if ( m_twitterAuth.isNull() )
 761        return;
 762    QTweetUserShow *userShowFetch = new QTweetUserShow( m_twitterAuth.data(), this );
 763    connect( userShowFetch, SIGNAL( parsedUserInfo( QTweetUser ) ), SLOT( avatarUserDataSlot( QTweetUser ) ) );
 764    userShowFetch->fetch( screenName );
 765}
 766
 767void
 768TwitterPlugin::avatarUserDataSlot( const QTweetUser &user )
 769{
 770    qDebug() << Q_FUNC_INFO;
 771    if ( user.profileImageUrl().isEmpty() || m_twitterAuth.isNull() )
 772        return;
 773
 774    QNetworkRequest request( user.profileImageUrl() );
 775    QNetworkReply *reply = m_twitterAuth.data()->networkAccessManager()->get( request );
 776    reply->setProperty( "screenname", user.screenName() );
 777    connect( reply, SIGNAL( finished() ), this, SLOT( profilePicReply() ) );
 778}
 779
 780void
 781TwitterPlugin::refreshProxy()
 782{
 783    if ( !m_twitterAuth.isNull() )
 784        m_twitterAuth.data()->setNetworkAccessManager( TomahawkUtils::nam() );
 785}
 786
 787void
 788TwitterPlugin::profilePicReply()
 789{
 790    qDebug() << Q_FUNC_INFO;
 791    QNetworkReply *reply = qobject_cast< QNetworkReply* >( sender() );
 792    if ( !reply || reply->error() != QNetworkReply::NoError || !reply->property( "screenname" ).isValid() )
 793    {
 794        qDebug() << Q_FUNC_INFO << " reply not valid or came back with error";
 795        return;
 796    }
 797    QString screenName = reply->property( "screenname" ).toString();
 798    QString friendlyName = '@' + screenName;
 799    QByteArray rawData = reply->readAll();
 800    QImage image;
 801    image.loadFromData( rawData, "PNG" );
 802    QPixmap pixmap = QPixmap::fromImage( image );
 803    m_cachedAvatars[screenName] = pixmap;
 804    emit avatarReceived( friendlyName, QPixmap::fromImage( image ) );
 805}
 806
 807void
 808TwitterPlugin::checkSettings()
 809{
 810    if ( m_state == Disconnected )
 811        return;
 812    disconnectPlugin();
 813    connectPlugin( false );
 814}
 815
 816
 817void
 818TwitterPlugin::setTwitterSavedDbid( const QString& dbid )
 819{
 820    TomahawkSettings::instance()->setValue( pluginId() + "/saveddbid", dbid );
 821}
 822
 823
 824QString
 825TwitterPlugin::twitterSavedDbid() const
 826{
 827    return TomahawkSettings::instance()->value( pluginId() + "/saveddbid", QString() ).toString();
 828}
 829
 830
 831QString
 832TwitterPlugin::twitterScreenName() const
 833{
 834    TomahawkSettings* s = TomahawkSettings::instance();
 835    s->beginGroup( pluginId() );
 836    QStringList keys = s->childKeys();
 837    if ( keys.contains( "ScreenName", Qt::CaseSensitive ) )
 838    {
 839        s->setValue( "screenname_tmp",
 840            s->value( "ScreenName" ).toString() );
 841        s->remove( "ScreenName" );
 842
 843        s->sync();
 844    }
 845    keys = s->childKeys();
 846    if ( keys.contains( "screenname_tmp", Qt::CaseSensitive ) )
 847    {
 848        s->setValue( "screenname",
 849            s->value( "screenname_tmp" ).toString() );
 850        s->remove( "screenname_tmp" );
 851
 852        s->sync();
 853    }
 854    s->endGroup();
 855
 856    return s->value( pluginId() + "/screenname" ).toString();
 857}
 858
 859void
 860TwitterPlugin::setTwitterScreenName( const QString& screenName )
 861{
 862    TomahawkSettings::instance()->setValue( pluginId() + "/screenname", screenName );
 863}
 864
 865QString
 866TwitterPlugin::twitterOAuthToken() const
 867{
 868    TomahawkSettings* s = TomahawkSettings::instance();
 869    s->beginGroup( pluginId() );
 870    QStringList keys = s->childKeys();
 871    if ( keys.contains( "OAuthToken", Qt::CaseSensitive ) )
 872    {
 873        s->setValue( "oauthtoken_tmp",
 874            s->value( "OAuthToken" ).toString() );
 875        s->remove( "OAuthToken" );
 876
 877        s->sync();
 878
 879    }
 880    keys = s->childKeys();
 881    if ( keys.contains( "oauthtoken_tmp", Qt::CaseSensitive ) )
 882    {
 883        s->setValue( "oauthtoken",
 884            s->value( "oauthtoken_tmp" ).toString() );
 885        s->remove( "oauthtoken_tmp" );
 886
 887        s->sync();
 888    }
 889    s->endGroup();
 890
 891    return s->value( pluginId() + "/oauthtoken" ).toString();
 892}
 893
 894void
 895TwitterPlugin::setTwitterOAuthToken( const QString& oauthtoken )
 896{
 897    TomahawkSettings::instance()->setValue( pluginId() + "/oauthtoken", oauthtoken );
 898}
 899
 900QString
 901TwitterPlugin::twitterOAuthTokenSecret() const
 902{
 903    TomahawkSettings* s = TomahawkSettings::instance();
 904    s->beginGroup( pluginId() );
 905    QStringList keys = s->childKeys();
 906    if ( keys.contains( "OAuthTokenSecret", Qt::CaseSensitive ) )
 907    {
 908        s->setValue( "oauthtokensecret_tmp",
 909            s->value( "OAuthTokenSecret" ).toString() );
 910        s->remove( "OAuthTokenSecret" );
 911
 912        s->sync();
 913    }
 914    keys = s->childKeys();
 915    if ( keys.contains( "oauthtokensecret_tmp", Qt::CaseSensitive ) )
 916    {
 917        s->setValue( "oauthtokensecret",
 918            s->value( "oauthtokensecret_tmp" ).toString() );
 919        s->remove( "oauthtokensecret_tmp" );
 920
 921        s->sync();
 922    }
 923    s->endGroup();
 924
 925    return s->value( pluginId() + "/oauthtokensecret" ).toString();
 926}
 927
 928void
 929TwitterPlugin::setTwitterOAuthTokenSecret( const QString& oauthtokensecret )
 930{
 931    TomahawkSettings::instance()->setValue( pluginId() + "/oauthtokensecret", oauthtokensecret );
 932}
 933
 934qint64
 935TwitterPlugin::twitterCachedFriendsSinceId() const
 936{
 937    TomahawkSettings* s = TomahawkSettings::instance();
 938    s->beginGroup( pluginId() );
 939    QStringList keys = s->childKeys();
 940    if ( keys.contains( "CachedFriendsSinceID", Qt::CaseSensitive ) )
 941    {
 942        s->setValue( "cachedfriendssinceid_tmp",
 943            s->value( "CachedFriendsSinceID" ).toLongLong() );
 944        s->remove( "CachedFriendsSinceID" );
 945
 946        s->sync();
 947    }
 948    keys = s->childKeys();
 949    if ( keys.contains( "cachedfriendssinceid_tmp", Qt::CaseSensitive ) )
 950    {
 951        s->setValue( "cachedfriendssinceid",
 952            s->value( "cachedfriendssinceid_tmp" ).toLongLong() );
 953        s->remove( "cachedfriendssinceid_tmp" );
 954
 955        s->sync();
 956    }
 957    s->endGroup();
 958
 959    return s->value( pluginId() + "/cachedfriendssinceid", 0 ).toLongLong();
 960}
 961
 962void
 963TwitterPlugin::setTwitterCachedFriendsSinceId( qint64 cachedId )
 964{
 965    TomahawkSettings::instance()->setValue( pluginId() + "/cachedfriendssinceid", cachedId );
 966}
 967
 968qint64
 969TwitterPlugin::twitterCachedMentionsSinceId() const
 970{
 971    TomahawkSettings* s = TomahawkSettings::instance();
 972    s->beginGroup( pluginId() );
 973    QStringList keys = s->childKeys();
 974    if ( keys.contains( "CachedMentionsSinceID", Qt::CaseSensitive ) )
 975    {
 976        s->setValue( "cachedmentionssinceid_tmp",
 977            s->value( "CachedMentionsSinceID" ).toLongLong() );
 978        s->remove( "CachedMentionsSinceID" );
 979
 980        s->sync();
 981    }
 982    keys = s->childKeys();
 983    if ( keys.contains( "cachedmentionssinceid_tmp", Qt::CaseSensitive ) )
 984    {
 985        s->setValue( "cachedmentionssinceid",
 986            s->value( "cachedmentionssinceid_tmp" ).toLongLong() );
 987        s->remove( "cachedmentionssinceid_tmp" );
 988
 989        s->sync();
 990    }
 991    s->endGroup();
 992
 993    return s->value( pluginId() + "/cachedmentionssinceid", 0 ).toLongLong();
 994}
 995
 996void
 997TwitterPlugin::setTwitterCachedMentionsSinceId( qint64 cachedId )
 998{
 999    TomahawkSettings::instance()->setValue( pluginId() + "/cachedmentionssinceid", cachedId );
1000}
1001
1002qint64
1003TwitterPlugin::twitterCachedDirectMessagesSinceId() const
1004{
1005    TomahawkSettings* s = TomahawkSettings::instance();
1006    s->beginGroup( pluginId() );
1007    QStringList keys = s->childKeys();
1008    if ( keys.contains( "CachedDirectMessagesSinceID", Qt::CaseSensitive ) )
1009    {
1010        s->setValue( "cacheddirectmessagessinceid_tmp",
1011            s->value( "CachedDirectMessagesSinceID" ).toLongLong() );
1012        s->remove( "CachedDirectMessagesSinceID" );
1013
1014        s->sync();
1015    }
1016    keys = s->childKeys();
1017    if ( keys.contains( "cacheddirectmessagessinceid_tmp", Qt::CaseSensitive ) )
1018    {
1019        s->setValue( "cacheddirectmessagessinceid",
1020            s->value( "cacheddirectmessagessinceid_tmp" ).toLongLong() );
1021        s->remove( "cacheddirectmessagessinceid_tmp" );
1022
1023        s->sync();
1024    }
1025    s->endGroup();
1026
1027    return s->value( pluginId() + "/cacheddirectmessagessinceid", 0 ).toLongLong();
1028}
1029
1030void
1031TwitterPlugin::setTwitterCachedDirectMessagesSinceId( qint64 cachedId )
1032{
1033    TomahawkSettings::instance()->setValue( pluginId() + "/cacheddirectmessagessinceid", cachedId );
1034}
1035
1036QVariantHash
1037TwitterPlugin::twitterCachedPeers() const
1038{
1039    TomahawkSettings* s = TomahawkSettings::instance();
1040    s->beginGroup( pluginId() );
1041    QStringList keys = s->childKeys();
1042    if ( keys.contains( "CachedPeers", Qt::CaseSensitive ) )
1043    {
1044        s->setValue( "cachedpeers_tmp",
1045            s->value( "CachedPeers" ).toHash() );
1046        s->remove( "CachedPeers" );
1047
1048        s->sync();
1049    }
1050    keys = s->childKeys();
1051    if ( keys.contains( "cachedpeers_tmp", Qt::CaseSensitive ) )
1052    {
1053        s->setValue( "cachedpeers",
1054            s->value( "cachedpeers_tmp" ).toHash() );
1055        s->remove( "cachedpeers_tmp" );
1056
1057        s->sync();
1058    }
1059    s->endGroup();
1060
1061    return s->value( pluginId() + "/cachedpeers", QVariantHash() ).toHash();
1062}
1063
1064void
1065TwitterPlugin::setTwitterCachedPeers( const QVariantHash &cachedPeers )
1066{
1067    TomahawkSettings::instance()->setValue( pluginId() + "/cachedpeers", cachedPeers );
1068    TomahawkSettings::instance()->sync();
1069}
1070
1071Q_EXPORT_PLUGIN2( sipfactory, TwitterFactory )