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