From 6cd9b282a3d11e302f33cb1e1dfe69b9ea9502c2 Mon Sep 17 00:00:00 2001 From: Ingersol Date: Sat, 19 Nov 2016 19:23:00 +0200 Subject: [PATCH 1/2] *hacked strange 'no dllimport allowed on static members' error somewhere in o2 moc files --- src/o0export.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/o0export.h b/src/o0export.h index 5af45cb..1a13cdd 100644 --- a/src/o0export.h +++ b/src/o0export.h @@ -5,7 +5,7 @@ #ifdef O2_DLL_EXPORT #define O0_EXPORT __declspec(dllexport) #else - #define O0_EXPORT __declspec(dllimport) + #define O0_EXPORT #endif #else #define O0_EXPORT From 2f8299b873031e9e72ad5ea0ce5970936b3156d5 Mon Sep 17 00:00:00 2001 From: Ingersol Date: Mon, 21 Nov 2016 13:04:06 +0200 Subject: [PATCH 2/2] +example message receiver --- examples/YoutubeChat/.gitignore | 73 +++++++ examples/YoutubeChat/YoutubeChat.pro | 34 +++ examples/YoutubeChat/commentmodel.cpp | 57 +++++ examples/YoutubeChat/commentmodel.h | 111 ++++++++++ examples/YoutubeChat/helper.cpp | 20 ++ examples/YoutubeChat/helper.h | 16 ++ examples/YoutubeChat/livebroadcastmodel.cpp | 49 +++++ examples/YoutubeChat/livebroadcastmodel.h | 41 ++++ examples/YoutubeChat/main.cpp | 20 ++ examples/YoutubeChat/messagereceiver.cpp | 28 +++ examples/YoutubeChat/messagereceiver.h | 24 +++ examples/YoutubeChat/o2youtube.cpp | 26 +++ examples/YoutubeChat/o2youtube.h | 19 ++ examples/YoutubeChat/youtubeapi.cpp | 212 +++++++++++++++++++ examples/YoutubeChat/youtubeapi.h | 73 +++++++ examples/YoutubeChat/youtubecontroller.cpp | 220 ++++++++++++++++++++ examples/YoutubeChat/youtubecontroller.h | 60 ++++++ 17 files changed, 1083 insertions(+) create mode 100644 examples/YoutubeChat/.gitignore create mode 100644 examples/YoutubeChat/YoutubeChat.pro create mode 100644 examples/YoutubeChat/commentmodel.cpp create mode 100644 examples/YoutubeChat/commentmodel.h create mode 100644 examples/YoutubeChat/helper.cpp create mode 100644 examples/YoutubeChat/helper.h create mode 100644 examples/YoutubeChat/livebroadcastmodel.cpp create mode 100644 examples/YoutubeChat/livebroadcastmodel.h create mode 100644 examples/YoutubeChat/main.cpp create mode 100644 examples/YoutubeChat/messagereceiver.cpp create mode 100644 examples/YoutubeChat/messagereceiver.h create mode 100644 examples/YoutubeChat/o2youtube.cpp create mode 100644 examples/YoutubeChat/o2youtube.h create mode 100644 examples/YoutubeChat/youtubeapi.cpp create mode 100644 examples/YoutubeChat/youtubeapi.h create mode 100644 examples/YoutubeChat/youtubecontroller.cpp create mode 100644 examples/YoutubeChat/youtubecontroller.h diff --git a/examples/YoutubeChat/.gitignore b/examples/YoutubeChat/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/examples/YoutubeChat/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/examples/YoutubeChat/YoutubeChat.pro b/examples/YoutubeChat/YoutubeChat.pro new file mode 100644 index 0000000..5f2f82f --- /dev/null +++ b/examples/YoutubeChat/YoutubeChat.pro @@ -0,0 +1,34 @@ +QT += core gui + + +include(../../src/src.pri) +greaterThan(QT_MAJOR_VERSION, 4) { + QT += widgets +} + + +CONFIG += c++11 + + +TARGET = YoutubeChat +CONFIG += console + +TEMPLATE = app + +SOURCES += main.cpp \ + commentmodel.cpp \ + livebroadcastmodel.cpp \ + o2youtube.cpp \ + youtubeapi.cpp \ + youtubecontroller.cpp \ + helper.cpp \ + messagereceiver.cpp + +HEADERS += \ + commentmodel.h \ + livebroadcastmodel.h \ + o2youtube.h \ + youtubeapi.h \ + youtubecontroller.h \ + helper.h \ + messagereceiver.h diff --git a/examples/YoutubeChat/commentmodel.cpp b/examples/YoutubeChat/commentmodel.cpp new file mode 100644 index 0000000..4d995f9 --- /dev/null +++ b/examples/YoutubeChat/commentmodel.cpp @@ -0,0 +1,57 @@ +#include "commentmodel.h" + +namespace youtube +{ +CommentModel::CommentModel(QObject *parent): QAbstractListModel(parent) +{ + +} + +QHash CommentModel::roleNames() const { + QHash roles; + roles[RoleCommentMessage] = "Message properties"; + roles[RoleCommentAuthor] = "Message author properties"; + return roles; +} + +void CommentModel::clearComments() +{ + beginRemoveRows(QModelIndex(), 0, std::max(0,rowCount() - 1)); + comments.clear(); + endRemoveRows(); +} + +void CommentModel::addComment(QVariantMap comment) +{ + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + comments.append(comment); + endInsertRows(); +} + +int CommentModel::rowCount(const QModelIndex &parent) const +{ + return comments.count(); +} + +QVariant CommentModel::data(const QModelIndex &index, int role) const +{ + QVariant result; + QVariantMap comment = comments[index.row()]; + switch (role) { + case Roles::RoleCommentMessage: + result = comment["snippet"]; + + /*foreach (const QVariant &v, broadcast.keys()) { + qWarning() << v.toString() << " : " << broadcast[v.toString()]; + }*/ + break; + case Roles::RoleCommentAuthor: + result = comment["authorDetails"]; + break; + default: + result = comment; + } + return result; +} + +} diff --git a/examples/YoutubeChat/commentmodel.h b/examples/YoutubeChat/commentmodel.h new file mode 100644 index 0000000..2d4e144 --- /dev/null +++ b/examples/YoutubeChat/commentmodel.h @@ -0,0 +1,111 @@ +#ifndef COMMENTMODEL_H +#define COMMENTMODEL_H + +#include +#include +#include + +#include + +namespace youtube +{ +//docs can be found here: https://developers.google.com/youtube/v3/live/docs/liveChatMessages +enum class CommentType +{ + TextMessage, + FanFunding, + MessageDeleted, + NewSponsor, + UserBanned, + //you can extend this class and define more types + //as youtube defines much more event types. + NotImplemented +}; + +//you can rewrite this class as it contain only fields author need. +//data example +/* + * "authorDetails": { + "channelId": "UCgCuI4JV0Imukb3ZcmC24kA", + "channelUrl": "http://www.youtube.com/channel/UCgCuI4JV0Imukb3ZcmC24kA", + "displayName": "Stas Artemjev", + "profileImageUrl": "https://yt3.ggpht.com/--esiXXT-93U/AAAAAAAAAAI/AAAAAAAAAAA/MReYWijclIg/s88-c-k-no-mo-rj-c0xffffff/photo.jpg", + "isVerified": false, + "isChatOwner": true, + "isChatSponsor": false, + "isChatModerator": false + } + * */ +struct CommentAuthor +{ + QString displayName; + QString profileImageUrl; + bool isChatOwner; + bool isChatSponsor; + bool isChatModerator; +}; + +//data example +/* + * "snippet": { + "type": "textMessageEvent", + "liveChatId": "EiEKGFVDZ0N1STRKVjBJbXVrYjNaY21DMjRrQRIFL2xpdmU", + "authorChannelId": "UCgCuI4JV0Imukb3ZcmC24kA", + "publishedAt": "2016-11-16T20:50:29.047Z", + "hasDisplayContent": true, + "displayMessage": "тест 3", + "textMessageDetails": { + "messageText": "тест 3" + } + * */ +struct CommentMessage +{ + QString text; + CommentType messageType; + bool canBeDisplayed; +}; + +struct CommentFundraisingDetails +{ + unsigned long amountMicros; + QString currency; + QString amountDisplayString; + QString userComment; +}; + + +class CommentModel:public QAbstractListModel +{ +public: + enum Roles { + //message text + RoleCommentMessage = Qt::UserRole + 1, + //Type of message: standart message, fanfunding event, new sponsor event etc + RoleCommentAuthor = Qt::UserRole +2, + }; + + CommentModel(QObject *parent = 0); + + /// Clear all comments + void clearComments(); + + /// Add a comment + void addComment(QVariantMap comment); + + /// Get number of comments + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + /// Access a comment + QVariant data(const QModelIndex & index, int role) const; + + /// Get role names + QHashroleNames() const; + + +protected: + QList comments; + + +}; +} +#endif // COMMENTMODEL_H diff --git a/examples/YoutubeChat/helper.cpp b/examples/YoutubeChat/helper.cpp new file mode 100644 index 0000000..894b889 --- /dev/null +++ b/examples/YoutubeChat/helper.cpp @@ -0,0 +1,20 @@ +#include "helper.h" + +using namespace youtube; +Helper::Helper(YoutubeController& controller,MessageReceiver& receiver,QObject * parent ): + youtube_(controller), + receiver_(receiver), + QObject(parent) +{ + connect(&youtube_,SIGNAL(error(const QString )),&receiver_,SLOT(onError(const QString))); + + connect(&youtube_,SIGNAL(message(const QString ,const QString ,const QString,bool , bool ,bool)), + &receiver_,SLOT(onNewMessage(const QString ,const QString ,const QString,bool , bool ,bool))); + + + + connect(&youtube_,SIGNAL(fundraising(const QString ,const QString,const QString,ulong ,QString ,QString )), + &receiver_,SLOT(onFundraising(const QString ,const QString,const QString,ulong ,QString ,QString ))); + + connect(&youtube_,SIGNAL(newSponsor(const QString )),&receiver_,SLOT(onNewSponsor(const QString))); +} diff --git a/examples/YoutubeChat/helper.h b/examples/YoutubeChat/helper.h new file mode 100644 index 0000000..52976ce --- /dev/null +++ b/examples/YoutubeChat/helper.h @@ -0,0 +1,16 @@ +#ifndef HELPER_H +#define HELPER_H + +#include "youtubecontroller.h" +#include "messagereceiver.h" +class Helper:public QObject +{ + Q_OBJECT +public: + Helper(youtube::YoutubeController& controller,MessageReceiver& receiver,QObject * parent ); +private: + youtube::YoutubeController& youtube_; + MessageReceiver& receiver_; +}; + +#endif // HELPER_H diff --git a/examples/YoutubeChat/livebroadcastmodel.cpp b/examples/YoutubeChat/livebroadcastmodel.cpp new file mode 100644 index 0000000..a8ba0bc --- /dev/null +++ b/examples/YoutubeChat/livebroadcastmodel.cpp @@ -0,0 +1,49 @@ +#include +#include "livebroadcastmodel.h" + +namespace youtube +{ +LiveBroadcastModel::LiveBroadcastModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +QHash LiveBroadcastModel::roleNames() const { + QHash roles; + roles[RoleLiveChatId] = "RoleLiveChatId"; + return roles; +} + +void LiveBroadcastModel::addBroadcast(QVariantMap broadcast) { + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + broadcasts.append(broadcast); + endInsertRows(); +} + +void LiveBroadcastModel::clearBroadcasts() { + beginRemoveRows(QModelIndex(), 0, std::max(0,rowCount() - 1)); + broadcasts.clear(); + endRemoveRows(); +} + +int LiveBroadcastModel::rowCount(const QModelIndex &) const { + return broadcasts.count(); +} + +QVariant LiveBroadcastModel::data(const QModelIndex &index, int role) const { + QVariant result; + QVariantMap broadcast = broadcasts[index.row()]; + switch (role) { + case LiveBroadcastModel::RoleLiveChatId: + result = broadcast["snippet"].toMap()["liveChatId"]; + + /*foreach (const QVariant &v, broadcast.keys()) { + qWarning() << v.toString() << " : " << broadcast[v.toString()]; + }*/ + break; + default: + result = broadcast; + } + return result; +} +} + diff --git a/examples/YoutubeChat/livebroadcastmodel.h b/examples/YoutubeChat/livebroadcastmodel.h new file mode 100644 index 0000000..c9ad2fd --- /dev/null +++ b/examples/YoutubeChat/livebroadcastmodel.h @@ -0,0 +1,41 @@ +#ifndef LIVEBOADCASTMODEL_H +#define LIVEBOADCASTMODEL_H + +#include +#include +#include +#include + +namespace youtube +{ +class LiveBroadcastModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum Roles { + RoleLiveChatId = Qt::UserRole + 1, + }; + + LiveBroadcastModel(QObject *parent = 0); + + /// Clear all broadcasts + void clearBroadcasts(); + + /// Add a broadcast + void addBroadcast(QVariantMap tweet); + + /// Get number of broadcasts + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + /// Access a broadcasts + QVariant data(const QModelIndex & index, int role = Roles::RoleLiveChatId) const; + + /// Get role names + QHashroleNames() const; + +protected: + QList broadcasts; + +}; +} +#endif // LIVEBOADCASTMODEL_H diff --git a/examples/YoutubeChat/main.cpp b/examples/YoutubeChat/main.cpp new file mode 100644 index 0000000..c6f8ce2 --- /dev/null +++ b/examples/YoutubeChat/main.cpp @@ -0,0 +1,20 @@ +#include +#include +#include +#include "youtubecontroller.h" +#include "helper.h" + +using namespace youtube; + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + YoutubeController youtube; + MessageReceiver receiver; + Helper helper(youtube,receiver,nullptr); + + QTimer::singleShot(0, &youtube, SLOT(doAuth())); + + return a.exec(); +} diff --git a/examples/YoutubeChat/messagereceiver.cpp b/examples/YoutubeChat/messagereceiver.cpp new file mode 100644 index 0000000..9045e37 --- /dev/null +++ b/examples/YoutubeChat/messagereceiver.cpp @@ -0,0 +1,28 @@ +#include +#include "messagereceiver.h" + +MessageReceiver::MessageReceiver(QObject * parent):QObject(parent) +{ + +} + +void MessageReceiver::onError(const QString error) +{ + qDebug() << "Error: " << error ; +} + +void MessageReceiver::onNewMessage(const QString message,const QString authorName,const QString authorImageLogo, + bool isChatModerator, bool isChatOwner,bool isChatSponsor) +{ + qDebug() << authorName << ": " < +#include +#include +#include + +#include "o2requestor.h" +#include "o2youtube.h" +#include "youtubeapi.h" +#include "commentmodel.h" +#include "LiveBroadcastModel.h" + +namespace youtube +{ +static const char * YOUTUBE_LIVE_PART = "part"; +static const char * YOUTUBE_LIVE_FILTER_BROADCAST_STATUS = "broadcastStatus"; +static const char * YOUTUBE_LIVE_BROADCAST_TYPE = "broadcastType"; +static const char * YOUTUBE_LIVE_FILTER_MINE = "mine"; +static const char * YOUTUBE_LIVE_FILTER_ID = "id"; +static const char * YOUTUBE_LIVE_OPTIONAL_MAXRESULTS = "maxResults"; + +static const char * YOUTUBE_LIVE_COMMENTS_PAGETOKEN = "pageToken"; +static const char * YOUTUBE_LIVE_COMMENTS_LIVECHATID = "liveChatId"; + + +YoutubeApi::YoutubeApi(QObject *parent): QObject(parent), + authenticator_(0), + lastPageToken_("") +{ + manager_ = new QNetworkAccessManager(this); + commentModel_ = new CommentModel(this); + broadcastModel_ = new LiveBroadcastModel(this); + lastMessageTimestamp_ = QDateTime::currentDateTimeUtc().addDays(-2); +} + +YoutubeApi::~YoutubeApi() { +} + +void YoutubeApi::cleanState() +{ + broadcastModel_->clearBroadcasts(); + commentModel_->clearComments(); + + //disable for debug purposes + //lastPageToken_ = ""; +} + +CommentModel *YoutubeApi::commentModel() { + return commentModel_; +} + +LiveBroadcastModel * YoutubeApi::broadcastModel() +{ + return broadcastModel_; +} + +O2Youtube *YoutubeApi::authenticator() const { + return authenticator_; +} +void YoutubeApi::setAuthenticator(O2Youtube *v) { + authenticator_ = v; +} + + +//request only our live broadcasts, single item +void YoutubeApi::requestOwnLiveBroadcasts() +{ + broadcastModel_->clearBroadcasts(); + + if (!authenticator_ || !authenticator_->linked()) { + emit broadcastModelChanged(); + return; + } + + O2Requestor *requestor = new O2Requestor(manager_, authenticator_, this); + QUrl url = QUrl("https://www.googleapis.com/youtube/v3/liveBroadcasts"); + + QUrlQuery query(url); + query.addQueryItem(YOUTUBE_LIVE_PART,"snippet"); + query.addQueryItem(YOUTUBE_LIVE_FILTER_MINE,"true"); + query.addQueryItem(YOUTUBE_LIVE_BROADCAST_TYPE,"persistent"); + query.addQueryItem(YOUTUBE_LIVE_OPTIONAL_MAXRESULTS,"1"); + + url.setQuery(query); + + QNetworkRequest request(url); + requestor->get(request); + + //connect(requestor, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestFailed(QNetworkReply::NetworkError)));//commentsReceived())); + connect(requestor, SIGNAL(finished(int, QNetworkReply::NetworkError, QByteArray)), + this, SLOT(broadcastRequestFinished(int, QNetworkReply::NetworkError, QByteArray)));//commentsReceived())); + + +} + +void YoutubeApi::broadcastRequestFinished(int id, QNetworkReply::NetworkError error, QByteArray data) +{ + (void)(id); + if (error != QNetworkReply::NetworkError::NoError) + { + emit requestFailed(error,data); + broadcastModel_->clearBroadcasts(); + emit broadcastModelChanged(); + return; + } + qWarning() << "data " << data; + QJsonDocument jsonResponse = QJsonDocument::fromJson(data); + QVariantMap variantData = jsonResponse.toVariant().toMap(); + + /*foreach (const QVariant &v, variantData.keys()) { + qWarning() << v.toString() << " : " << variantData[v.toString()]; + }*/ + + auto broadcastsData = variantData["items"].toList(); + + foreach (const QVariant &v, broadcastsData) { + auto broadcastData = v.toMap(); + broadcastModel_->addBroadcast(broadcastData); + } + + emit broadcastModelChanged(); + +} + + + +void YoutubeApi::requestComments(QString liveCommentsId,QString lastPageToken) { + commentModel_->clearComments(); + + if (!authenticator_ || !authenticator_->linked()) { + emit commentModelChanged(); + return; + } + + O2Requestor *requestor = new O2Requestor(manager_, authenticator_, this); + QUrl url = QUrl("https://www.googleapis.com/youtube/v3/liveChat/messages"); + + QUrlQuery query(url); + query.addQueryItem(YOUTUBE_LIVE_PART,"snippet,authorDetails"); + + //set page size to minimum of 200 as we will take one page each time we getting comments + query.addQueryItem(YOUTUBE_LIVE_OPTIONAL_MAXRESULTS,"200"); + + //retrieve *last* message page + if (lastPageToken.size()>0) + query.addQueryItem(YOUTUBE_LIVE_COMMENTS_PAGETOKEN,"lastPageToken"); + + query.addQueryItem(YOUTUBE_LIVE_COMMENTS_LIVECHATID,liveCommentsId); + + url.setQuery(query); + + QNetworkRequest request(url); + requestor->get(request); + + connect(requestor, SIGNAL(finished(int, QNetworkReply::NetworkError, QByteArray)), this, SLOT(commentRequestFinished(int, QNetworkReply::NetworkError, QByteArray)));//commentsReceived())); + //connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestFailed(QNetworkReply::NetworkError))); +} + + + +void YoutubeApi::commentRequestFinished(int id, QNetworkReply::NetworkError error, QByteArray data) +{ + (void)(id); + if (error != QNetworkReply::NetworkError::NoError) + { + emit requestFailed(error,data); + commentModel_->clearComments(); + emit commentModelChanged(); + return; + } + + qWarning() << "data " << data; + QJsonDocument jsonResponse = QJsonDocument::fromJson(data); + QVariantMap variantData = jsonResponse.toVariant().toMap(); + + /*foreach (const QVariant &v, variantData.keys()) { + qWarning() << v.toString() << " : " << variantData[v.toString()]; + }*/ + + //getting lastPageToken for future comment requests + lastPageToken_ = variantData["nextPageToken"].toString(); + + + auto commentsData = variantData["items"].toList(); + + foreach (const QVariant &v, commentsData) { + auto commentData = v.toMap(); + + //skip every comment older than (lastMessageTimestamp_) or IGNORE_MESSAGES_OLDER_THAN_SEC + + //todo: should be optimized as we make commentData["snippet"].toMap in commentModel too + QDateTime commentTimestamp = QDateTime::fromString(commentData["snippet"].toMap()["publishedAt"].toString(),Qt::ISODate); + if (commentTimestamp.toUTC()<=lastMessageTimestamp_.toUTC()) + continue; + + if (commentTimestamp.toUTC().addSecs(IGNORE_MESSAGES_OLDER_THAN_SEC)addComment(commentData); + } + + emit commentModelChanged(); + +} + +//obsolete in O2Requestor? +void YoutubeApi::requestFailed(QNetworkReply::NetworkError error,QByteArray data) { + + qWarning() << "YoutubeApi::requestFailed:" << (int)error << error < +#include +#include "livebroadcastmodel.h" + +class QNetworkAccessManager; + +namespace youtube +{ + +//ignore messages older than 10 minutes +const unsigned int IGNORE_MESSAGES_OLDER_THAN_SEC = 10*60; + +class CommentModel; +class O2Youtube; + + +class YoutubeApi : public QObject +{ + Q_OBJECT +public: + /// List of comments + Q_PROPERTY(CommentModel *commentModel READ commentModel NOTIFY commentModelChanged) + CommentModel *commentModel() ; + Q_PROPERTY(LiveBroadcastModel *commentModel READ broadcastModel NOTIFY broadcastModelChanged) + LiveBroadcastModel *broadcastModel() ; + + /// OAuth authenticator + Q_PROPERTY(O2Youtube *authenticator READ authenticator WRITE setAuthenticator) + O2Youtube *authenticator() const; + void setAuthenticator(O2Youtube *v) ; + + explicit YoutubeApi(QObject *parent = 0); + virtual ~YoutubeApi(); + + //empty broadcast/comments models, + //wipe page token. + void cleanState(); + //request livebroadcast id that behold user's account + void requestOwnLiveBroadcasts(); + void requestComments(QString liveCommentsId,QString lastPageToken=""); +protected: + + O2Youtube *authenticator_; + CommentModel *commentModel_; + LiveBroadcastModel *broadcastModel_; + + QNetworkAccessManager *manager_; +private: + + //token that keeps which comments parts we need + QString lastPageToken_; + QDateTime lastMessageTimestamp_; + + + +signals: + void commentModelChanged(); + void broadcastModelChanged(); + // void comments() + + +protected slots: + //obsolete in O2Requestor? + void requestFailed(QNetworkReply::NetworkError error,QByteArray data); + Q_INVOKABLE virtual void broadcastRequestFinished(int id, QNetworkReply::NetworkError error, QByteArray data); + Q_INVOKABLE virtual void commentRequestFinished(int id, QNetworkReply::NetworkError error, QByteArray data); + +}; +} +#endif // YOUTUBEAPI_H diff --git a/examples/YoutubeChat/youtubecontroller.cpp b/examples/YoutubeChat/youtubecontroller.cpp new file mode 100644 index 0000000..c041515 --- /dev/null +++ b/examples/YoutubeChat/youtubecontroller.cpp @@ -0,0 +1,220 @@ +#include "youtubecontroller.h" +#include "o2youtube.h" +#include +#include "youtubeapi.h" +#include "o2youtube.h" +#include + +namespace youtube +{ +const int DEFAULT_YOUTUBE_UPDATE_MESSAGES_INTERVAL_MS = 3000; +YoutubeController::YoutubeController(QObject *parent): QObject(parent), + commentsTimerId_(0), + updateMessagesIntervalMs_(DEFAULT_YOUTUBE_UPDATE_MESSAGES_INTERVAL_MS), + liveChatId_("") +{ + o2youtube_ = new O2Youtube(this); + + o2youtube_->setClientId("463300085238-11h8th4cbl9uoi4rd8pcl0e19sspn1s8.apps.googleusercontent.com"); + o2youtube_->setClientSecret("PZE0m3Ym4lZRKWDWk6KQ9c7T"); + + + connect(o2youtube_, SIGNAL(linkedChanged()), this, SLOT(onLinkedChanged())); + connect(o2youtube_, SIGNAL(linkingFailed()), this, SLOT(onLinkingFailed())); + connect(o2youtube_, SIGNAL(linkingSucceeded()), this, SLOT(onLinkingSucceeded())); + connect(o2youtube_, SIGNAL(openBrowser(QUrl)), this, SLOT(onOpenBrowser(QUrl))); + connect(o2youtube_, SIGNAL(closeBrowser()), this, SLOT(onCloseBrowser())); + + api_ = new YoutubeApi(this); + connect(api_, SIGNAL(broadcastModelChanged()),this, SLOT(broadcastModelChanged())); + connect(api_, SIGNAL(commentModelChanged()),this, SLOT(commentModelChanged())); +} + +void YoutubeController::doAuth() +{ + //o2youtube_->unlink(); + o2youtube_->link(); +} +void YoutubeController::delogin() +{ + //o2youtube_->unlink(); + stopPolling(); + o2youtube_->unlink(); +} +void YoutubeController::onLinkedChanged() +{ +} + +void YoutubeController::onLinkingFailed() +{ + emit error(tr("Cannot login to youtube services.")) ; +} + + +void YoutubeController::onLinkingSucceeded() +{ + //bug? + //o2 emit linkingSucceed in unlink() too + if (!o2youtube_->linked()) + return; + + emit message(QApplication::tr("Connecting to youtube channel..."),"",""); + api_->setAuthenticator(o2youtube_); + api_->requestOwnLiveBroadcasts(); +} + +void YoutubeController::onOpenBrowser(const QUrl &url) +{ + QDesktopServices::openUrl(url); + +} + +void YoutubeController::onCloseBrowser() +{ + +} + +void YoutubeController::onFinished() +{ + +} + + +//#Broadcast region +void YoutubeController::broadcastModelChanged() +{ + const auto& broadcastModel = api_->broadcastModel(); + if (broadcastModel->rowCount() ==0) + { + emit error(tr("No broadcasts found")); + return; + } + + QString liveChatId = broadcastModel->data(broadcastModel->index(0,0)).toString(); + + if (liveChatId.size()==0) + { + emit error(tr("Broadcast has no live chats attached")); + return; + } + + emit message(QApplication::tr("Connected to youtube channel."),"",""); + liveChatId_ = liveChatId; + startPolling(); + +} + +void YoutubeController::timerEvent( QTimerEvent * event ) +{ + (void)event; + api_->requestComments(liveChatId_); +} + +void YoutubeController::commentModelChanged() +{ + const auto& commentModel = api_->commentModel(); + + for(int k = 0; k!=commentModel->rowCount();k++) + { + auto& rawMessage = commentModel->data(commentModel->index(k,0),CommentModel::Roles::RoleCommentMessage).toMap(); + auto& rawAuthor = commentModel->data(commentModel->index(k,0),CommentModel::Roles::RoleCommentAuthor).toMap(); + CommentMessage msg = getCommentMessageFromRawData(rawMessage); + CommentAuthor author = getCommentAuthorFromRawData(rawAuthor); + switch (msg.messageType) + { + case CommentType::TextMessage: + { + if (!msg.canBeDisplayed) + return; + + QString messageText = rawMessage["textMessageDetails"].toMap()["messageText"].toString(); + QString authorName = author.displayName; + emit message(messageText,authorName,author.profileImageUrl, + author.isChatModerator, author.isChatOwner,author.isChatSponsor); + } + break; + case CommentType::FanFunding: + { + auto fundsDetails = getCommentFundraisingDetailsFromRawData(rawMessage); + emit fundraising(fundsDetails.userComment,author.displayName,author.profileImageUrl + ,fundsDetails.amountMicros,fundsDetails.currency,fundsDetails.amountDisplayString); + } + break; + case CommentType::NewSponsor: + { + emit newSponsor(author.displayName); + } + break; + // ignore rest of message types. + default: break; + } + } +} + +//filling only few CommentMessage fields +CommentMessage YoutubeController::getCommentMessageFromRawData(const QVariantMap& data) +{ + CommentMessage msg; + QString messageType = data["type"].toString(); + + if (messageType.startsWith("textMessageEvent")) + msg.messageType = CommentType::TextMessage; + else + if (messageType.startsWith("fanFundingEvent")) + msg.messageType = CommentType::FanFunding; + else + if (messageType.startsWith("newSponsorEvent")) + msg.messageType = CommentType::NewSponsor; + else + if (messageType.startsWith("messageDeletedEvent")) + msg.messageType = CommentType::MessageDeleted; + else + if (messageType.startsWith("userBannedEvent")) + msg.messageType = CommentType::UserBanned; + else + msg.messageType = CommentType::NotImplemented; + + msg.canBeDisplayed = data["hasDisplayContent"].toBool(); + + + return msg; +} +CommentAuthor YoutubeController::getCommentAuthorFromRawData(const QVariantMap& data) +{ + CommentAuthor msg; + msg.displayName = data["displayName"].toString(); + msg.isChatModerator = data["isChatModerator"].toBool(); + msg.isChatOwner = data["isChatOwner"].toBool(); + msg.isChatSponsor = data["isChatSponsor"].toBool(); + msg.profileImageUrl = data["profileImageUrl"].toString(); + return msg; +} + +CommentFundraisingDetails YoutubeController::getCommentFundraisingDetailsFromRawData(const QVariantMap& data) +{ + CommentFundraisingDetails fundraisingDetails; + const auto rawDetails = data["fanFundingEventDetails"].toMap(); + + fundraisingDetails.amountMicros = rawDetails["amountMicros"].toUInt(); + fundraisingDetails.amountDisplayString = rawDetails["amountDisplayString"].toString(); + fundraisingDetails.currency = rawDetails["currency"].toString(); + fundraisingDetails.userComment = rawDetails["userComment"].toString(); + + return fundraisingDetails; +} + +void YoutubeController::startPolling() +{ + commentsTimerId_ = startTimer(updateMessagesIntervalMs_); +} + +void YoutubeController::stopPolling() +{ + if (commentsTimerId_==0) + return; + + killTimer(commentsTimerId_); + commentsTimerId_ = 0; +} + +} diff --git a/examples/YoutubeChat/youtubecontroller.h b/examples/YoutubeChat/youtubecontroller.h new file mode 100644 index 0000000..9af92b9 --- /dev/null +++ b/examples/YoutubeChat/youtubecontroller.h @@ -0,0 +1,60 @@ +#ifndef YOTUBEAUTHENTIFICATOR_H +#define YOTUBEAUTHENTIFICATOR_H +#include + +#include "commentmodel.h" +namespace youtube +{ +class O2Youtube; +class YoutubeApi; +class YoutubeController:public QObject +{ +Q_OBJECT +public: + YoutubeController(QObject *parent = nullptr); + void delogin(); + +signals: + void error(const QString errorMessage); + void message(const QString message,const QString authorName,const QString authorImageLogo, + bool isChatModerator = false, bool isChatOwner = false,bool isChatSponsor = false); + void fundraising(const QString userComment,const QString authorName,const QString authorImageLogo + ,ulong amountMicros,QString currency,QString amountDisplayString); + void newSponsor(const QString authorName); + +public slots: + void doAuth(); +protected: + virtual void timerEvent( QTimerEvent * event ); +private slots: + void onLinkedChanged(); + void onLinkingSucceeded(); + void onLinkingFailed(); + void onOpenBrowser(const QUrl &url); + void onCloseBrowser(); + void onFinished(); + void broadcastModelChanged(); + void commentModelChanged(); + +private: + static CommentMessage getCommentMessageFromRawData(const QVariantMap& data); + static CommentAuthor getCommentAuthorFromRawData(const QVariantMap& data); + static CommentFundraisingDetails getCommentFundraisingDetailsFromRawData(const QVariantMap& data); + + void startPolling(); + void stopPolling(); + + O2Youtube* o2youtube_; + YoutubeApi * api_; + + QString liveChatId_; + + //timer id for comments polling loop + int commentsTimerId_; + + int updateMessagesIntervalMs_; + + +}; +} +#endif // YOTUBEAUTHENTIFICATOR_H