PageRenderTime 1157ms CodeModel.GetById 161ms app.highlight 861ms RepoModel.GetById 89ms app.codeStats 1ms

/src/xmppbot/xmppbot.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 452 lines | 376 code | 53 blank | 23 comment | 70 complexity | 25527331d6b2573699f33dde1e720a2d 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 *   Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
  5 *
  6 *   Tomahawk is free software: you can redistribute it and/or modify
  7 *   it under the terms of the GNU General Public License as published by
  8 *   the Free Software Foundation, either version 3 of the License, or
  9 *   (at your option) any later version.
 10 *
 11 *   Tomahawk is distributed in the hope that it will be useful,
 12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 14 *   GNU General Public License for more details.
 15 *
 16 *   You should have received a copy of the GNU General Public License
 17 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 18 */
 19
 20#include "XmppBot.h"
 21
 22#include "infosystem/InfoSystem.h"
 23#include "Album.h"
 24#include "Typedefs.h"
 25#include "TomahawkSettings.h"
 26
 27#include "audio/AudioEngine.h"
 28
 29#include <gloox/client.h>
 30#include <gloox/rostermanager.h>
 31#include <gloox/message.h>
 32#include <gloox/connectiontcpclient.h>
 33
 34#include <QtCore/QStringList>
 35
 36#include "utils/Logger.h"
 37
 38using namespace gloox;
 39using namespace Tomahawk::InfoSystem;
 40
 41static QString s_botInfoIdentifier = QString( "XMPPBot" );
 42
 43
 44XMPPBot::XMPPBot(QObject *parent)
 45    : QObject(parent)
 46    , m_currReturnMessage("\n")
 47{
 48    qDebug() << Q_FUNC_INFO;
 49    TomahawkSettings *settings = TomahawkSettings::instance();
 50    QString server = settings->xmppBotServer();
 51    QString jidstring = settings->xmppBotJid();
 52    QString password = settings->xmppBotPassword();
 53    int port = settings->xmppBotPort();
 54    if (jidstring.isEmpty() || password.isEmpty())
 55        return;
 56
 57    JID jid(jidstring.toStdString());
 58    jid.setResource( QString( "tomahawkbot%1" ).arg( qrand() ).toStdString() );
 59
 60    m_client = new XMPPBotClient(this, jid, password.toStdString(), port);
 61    if (!server.isEmpty())
 62        m_client.data()->setServer(server.toStdString());
 63
 64    m_client.data()->registerConnectionListener(this);
 65    m_client.data()->registerSubscriptionHandler(this);
 66    m_client.data()->registerMessageHandler(this);
 67    m_client.data()->setPresence(Presence::Available, 1, "Tomahawkbot available");
 68
 69    connect(AudioEngine::instance(), SIGNAL(started(const Tomahawk::result_ptr &)),
 70            SLOT(newTrackSlot(const Tomahawk::result_ptr &)));
 71
 72    connect(InfoSystem::instance(),
 73        SIGNAL(info(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, QVariantMap)),
 74        SLOT(infoReturnedSlot(QString, Tomahawk::InfoSystem::InfoType, QVariant, QVariant, QVariantMap)));
 75
 76    connect(InfoSystem::instance(), SIGNAL(finished(QString)), SLOT(infoFinishedSlot(QString)));
 77
 78    bool success = m_client.data()->gloox::Client::connect(false);
 79    if (success)
 80        m_client.data()->run();
 81    else
 82        qDebug() << "XMPPBot failed to connect with Client";
 83}
 84
 85XMPPBot::~XMPPBot()
 86{
 87    qDebug() << Q_FUNC_INFO;
 88    if (!m_client.isNull())
 89        m_client.data()->gloox::Client::disconnect();
 90}
 91
 92void XMPPBot::newTrackSlot(const Tomahawk::result_ptr &track)
 93{
 94    m_currTrack = track;
 95    if (!track)
 96        return;
 97    QString status = QString("%1 - %2 (%3)")
 98                    .arg(track->artist()->name())
 99                    .arg(track->track())
100                    .arg(track->album()->name());
101
102    m_client.data()->setPresence(Presence::Available, 1, status.toStdString());
103}
104
105void XMPPBot::onConnect()
106{
107    qDebug() << Q_FUNC_INFO;
108    qDebug() << "XMPPBot Connected";
109}
110
111void XMPPBot::onDisconnect(ConnectionError e)
112{
113    qDebug() << Q_FUNC_INFO;
114    qDebug() << "XMPPBot Disconnected";
115    qDebug() << "Connection error msg:" << e;
116}
117
118bool XMPPBot::onTLSConnect(const gloox::CertInfo& info)
119{
120    //WARNING: Blindly accepts all certificates, at the moment
121    qDebug() << Q_FUNC_INFO;
122    return true;
123}
124
125void XMPPBot::handleSubscription(const gloox::Subscription& subscription)
126{
127    qDebug() << Q_FUNC_INFO;
128    if (subscription.subtype() == Subscription::Subscribed)
129    {
130        qDebug() << "XMPPBot is now subscribed to " << subscription.from().bare().c_str();
131        return;
132    }
133    else if(subscription.subtype() == Subscription::Unsubscribed)
134    {
135        qDebug() << "XMPPBot is now unsubscribed from " << subscription.from().bare().c_str();
136        return;
137    }
138    else if(subscription.subtype() == Subscription::Subscribe)
139    {
140        m_client.data()->rosterManager()->ackSubscriptionRequest(subscription.from().bareJID(), true);
141        m_client.data()->rosterManager()->subscribe(subscription.from().bareJID(), EmptyString, StringList(), "Let me in?");
142    }
143    else if(subscription.subtype() == Subscription::Unsubscribe)
144    {
145        m_client.data()->rosterManager()->ackSubscriptionRequest(subscription.from().bareJID(), true);
146        m_client.data()->rosterManager()->unsubscribe(subscription.from().bareJID(), "Sorry to see you go.");
147    }
148}
149
150void XMPPBot::handleMessage(const Message& msg, MessageSession* session)
151{
152    //TODO: implement "properly" with MessageSessions, if the bot is to be multi-user
153    if ( msg.subtype() != Message::Chat || msg.from().full().empty() || msg.to().full().empty() )
154        return;
155
156    QString body = QString::fromStdString( msg.body() ).toLower().trimmed();
157    QString originatingJid = QString::fromStdString( msg.from().full() );
158
159    if ( body.startsWith( "play" ) )
160    {
161        QStringList tokens = body.right( body.length() - 5 ).split( QString( "-" ), QString::SkipEmptyParts );
162        if ( tokens.count() < 2 )
163            AudioEngine::instance()->play();
164
165        Tomahawk::query_ptr q = Tomahawk::Query::get( tokens.first().trimmed(), tokens.last().trimmed(), QString() );
166        if ( q.isNull() )
167            return;
168
169        connect( q.data(), SIGNAL( resultsAdded( QList<Tomahawk::result_ptr> ) ),
170                             SLOT( onResultsAdded( QList<Tomahawk::result_ptr> ) ) );
171
172        return;
173    }
174    else if ( body.startsWith( "stop" ) )
175    {
176        AudioEngine::instance()->stop();
177        return;
178    }
179    else if ( body.startsWith( "prev" ) )
180    {
181        AudioEngine::instance()->previous();
182        return;
183    }
184    else if ( body.startsWith( "next" ) )
185    {
186        AudioEngine::instance()->next();
187        return;
188    }
189    else if ( body.startsWith( "pause" ) )
190    {
191        AudioEngine::instance()->pause();
192        return;
193    }
194
195    QStringList tokens( body.split( QString( " and " ), QString::SkipEmptyParts ) );
196    if ( tokens.isEmpty() )
197        return;
198
199    qDebug() << "jid from:" << QString::fromStdString(msg.from().full()) << ", jid to:" << QString::fromStdString(msg.to().full());
200    qDebug() << "Operating on tokens:" << tokens;
201
202    if (m_currTrack.isNull() || m_currTrack->artist()->name().isEmpty() || m_currTrack->track().isEmpty())
203    {
204        qDebug() << "XMPPBot can't figure out track";
205        QString m_currReturnMessage("\n\nSorry, I can't figure out what track is playing.\n\n");
206        Message retMsg(Message::Chat, JID(originatingJid.toStdString()), m_currReturnMessage.toStdString());
207        m_client.data()->send(retMsg);
208        return;
209    }
210
211    InfoTypeMap infoMap;
212    Q_FOREACH(QString token, tokens)
213    {
214        if (token == "biography")
215            infoMap[InfoArtistBiography] = m_currTrack.data()->artist()->name();
216        if (token == "terms")
217            infoMap[InfoArtistTerms] = m_currTrack.data()->artist()->name();
218        if (token == "hotttness")
219            infoMap[InfoArtistHotttness] = m_currTrack.data()->artist()->name();
220        if (token == "familiarity")
221            infoMap[InfoArtistFamiliarity] = m_currTrack.data()->artist()->name();
222        if (token == "lyrics")
223        {
224            QVariantMap myhash;
225            myhash["trackName"] = QVariant::fromValue<QString>(m_currTrack.data()->track());
226            myhash["artistName"] = QVariant::fromValue<QString>(m_currTrack.data()->artist()->name());
227            infoMap[InfoTrackLyrics] = QVariant::fromValue<QVariantMap>(myhash);
228        }
229    }
230
231    if (infoMap.isEmpty())
232    {
233        qDebug() << "XMPPBot can't figure out track";
234        QString m_currReturnMessage("\n\nSorry, I couldn't recognize any commands.\n\n");
235        Message retMsg(Message::Chat, JID(originatingJid.toStdString()), m_currReturnMessage.toStdString());
236        m_client.data()->send(retMsg);
237        return;
238    }
239
240    m_currInfoMap.unite(infoMap);
241    QString waitMsg("Please wait...");
242    Message retMsg(Message::Chat, JID(originatingJid.toStdString()), waitMsg.toStdString());
243    m_client.data()->send(retMsg);
244    QVariantMap hash;
245    hash["XMPPBotSendToJID"] = originatingJid;
246    InfoSystem::instance()->getInfo(s_botInfoIdentifier, infoMap, hash);
247}
248
249void XMPPBot::infoReturnedSlot(QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input, QVariant output, QVariantMap customData)
250{
251    qDebug() << Q_FUNC_INFO;
252
253    if (caller != s_botInfoIdentifier ||
254        input.isNull() || !input.isValid() ||
255        !customData.contains("XMPPBotSendToJID")
256       )
257    {
258        qDebug() << "Not the right object, custom data is null, or don't have a set JID";
259        return;
260    }
261
262    if (!m_currInfoMap.contains(type))
263    {
264        qDebug() << "not in currInfoMap";
265        return;
266    }
267    else
268        m_currInfoMap.remove(type);
269
270    QString jid = customData["XMPPBotSendToJID"].toString();
271    if (!m_currReturnJid.isEmpty() && m_currReturnJid != jid && !m_currReturnMessage.isEmpty())
272    {
273        gloox::Message msg(Message::Chat, JID(jid.toStdString()), m_currReturnMessage.toStdString());
274        m_client.data()->send(msg);
275        m_currReturnMessage = QString("\n");
276    }
277    m_currReturnJid = jid;
278
279    switch(type)
280    {
281        case InfoArtistBiography:
282        {
283            qDebug() << "Artist bio requested";
284            if (!output.canConvert<Tomahawk::InfoSystem::InfoGenericMap>() ||
285                !input.canConvert<QString>()
286               )
287            {
288                qDebug() << "Variants failed to be valid";
289                break;
290            }
291            InfoGenericMap bmap = output.value<Tomahawk::InfoSystem::InfoGenericMap>();
292            QString artist = input.toString();
293            m_currReturnMessage += QString("\nBiographies for %1\n").arg(artist);
294            Q_FOREACH(QString source, bmap.keys())
295            {
296                m_currReturnMessage += (bmap[source]["attribution"].isEmpty() ?
297                        QString("From %1:\n").arg(bmap[source]["site"]) :
298                        QString("From %1 at %2:\n").arg(bmap[source]["attribution"]).arg(bmap[source]["site"]));
299                m_currReturnMessage += bmap[source]["text"] + QString("\n");
300            }
301            break;
302        }
303        case InfoArtistTerms:
304        {
305            qDebug() << "Artist terms requested";
306            if (!output.canConvert<Tomahawk::InfoSystem::InfoGenericMap>() ||
307                !input.canConvert<QString>()
308               )
309            {
310                qDebug() << "Variants failed to be valid";
311                break;
312            }
313            InfoGenericMap tmap = output.value<Tomahawk::InfoSystem::InfoGenericMap>();
314            QString artist = input.toString();
315            m_currReturnMessage += tr("\nTerms for %1:\n").arg(artist);
316            if (tmap.isEmpty())
317                m_currReturnMessage += tr("No terms found, sorry.");
318            else
319            {
320                bool first = true;
321                Q_FOREACH(QString term, tmap.keys())
322                {
323                    if (first)
324                        m_currReturnMessage += (first ?
325                            QString("%1 (weight %2, frequency %3)")
326                                .arg(term).arg(tmap[term]["weight"]).arg(tmap[term]["frequency"])
327                              :
328                            QString("\n%1 (weight %2, frequency %3)")
329                                .arg(term).arg(tmap[term]["weight"]).arg(tmap[term]["frequency"])
330                              );
331                    first = false;
332                }
333                m_currReturnMessage += QString("\n");
334            }
335            break;
336        }
337        case InfoArtistHotttness:
338        {
339            qDebug() << "Artist hotttness requested";
340            if (!output.canConvert<qreal>() ||
341                !input.canConvert<QString>()
342               )
343            {
344                qDebug() << "Variants failed to be valid";
345                break;
346            }
347            QString artist = input.toString();
348            qreal retVal = output.toReal();
349            QString retValString = (retVal == 0.0 ? "(none)" : QString::number(retVal));
350            m_currReturnMessage += tr("\nHotttness for %1: %2\n").arg(artist, retValString);
351            break;
352        }
353        case InfoArtistFamiliarity:
354        {
355            qDebug() << "Artist familiarity requested";
356            if (!output.canConvert<qreal>() ||
357                !input.canConvert<QString>()
358               )
359            {
360                qDebug() << "Variants failed to be valid";
361                break;
362            }
363            QString artist = input.toString();
364            qreal retVal = output.toReal();
365            QString retValString = (retVal == 0.0 ? "(none)" : QString::number(retVal));
366            m_currReturnMessage += tr("\nFamiliarity for %1: %2\n").arg(artist, retValString);
367            break;
368        }
369        case InfoTrackLyrics:
370        {
371            qDebug() << "Lyrics requested";
372            if (!output.canConvert<QString>() ||
373                !input.canConvert<QVariantMap>()
374               )
375            {
376                qDebug() << "Variants failed to be valid";
377                break;
378            }
379            QVariantMap inHash = input.value<QVariantMap>();
380            QString artist = inHash["artistName"].toString();
381            QString track = inHash["trackName"].toString();
382            QString lyrics = output.toString();
383            qDebug() << "lyrics = " << lyrics;
384            m_currReturnMessage += tr("\nLyrics for \"%1\" by %2:\n\n%3\n").arg(track, artist, lyrics);
385            break;
386        }
387        default:
388            break;
389    }
390
391    if (m_currReturnMessage.isEmpty())
392    {
393        qDebug() << "Empty message, not sending anything back";
394        return;
395    }
396
397    qDebug() << "Going to send message: " << m_currReturnMessage << " to " << jid;
398
399    //gloox::Message msg(Message::Chat, JID(jid.toStdString()), m_currReturnMessage.toStdString());
400    //m_client.data()->send(msg);
401}
402
403void XMPPBot::infoFinishedSlot(QString caller)
404{
405    qDebug() << Q_FUNC_INFO;
406    qDebug() << "current return message is" << m_currReturnMessage;
407    qDebug() << "id is" << caller << "and our id is" << s_botInfoIdentifier;
408    if (m_currReturnMessage.isEmpty() || caller != s_botInfoIdentifier)
409        return;
410
411    qDebug() << "Sending message to JID" << m_currReturnJid;
412    gloox::Message msg(Message::Chat, JID(m_currReturnJid.toStdString()), m_currReturnMessage.toStdString());
413    m_client.data()->send(msg);
414    m_currReturnMessage = QString("\n");
415    m_currReturnJid.clear();
416}
417
418
419void XMPPBot::onResultsAdded( const QList<Tomahawk::result_ptr>& result )
420{
421    AudioEngine::instance()->playItem( 0, result.first() );
422}
423
424///////////////////////////////////////////////////////////////////////////////////////
425
426XMPPBotClient::XMPPBotClient(QObject *parent, JID &jid, std::string password, int port)
427    : QObject(parent)
428    , Client(jid, password, port)
429    , m_timer(this)
430{
431    qDebug() << Q_FUNC_INFO;
432}
433
434XMPPBotClient::~XMPPBotClient()
435{
436    qDebug() << Q_FUNC_INFO;
437}
438
439void XMPPBotClient::run()
440{
441    qDebug() << Q_FUNC_INFO;
442    QObject::connect(&m_timer, SIGNAL(timeout()), SLOT(recvSlot()));
443    m_timer.start(200);
444    qDebug() << "XMPPBot running";
445}
446
447void XMPPBotClient::recvSlot()
448{
449    gloox::ConnectionError error = recv(100);
450    if (error != gloox::ConnNoError)
451        qDebug() << "ERROR: in XMPPBotClient::recvSlot" << error;
452}