Hello community, here is the log from the commit of package orion for openSUSE:Factory checked in at 2017-10-17 01:53:54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/orion (Old) and /work/SRC/openSUSE:Factory/.orion.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "orion" Tue Oct 17 01:53:54 2017 rev:12 rq:534220 version:1.6.1+git~20171007 Changes: -------- --- /work/SRC/openSUSE:Factory/orion/orion.changes 2017-09-09 20:26:52.147262606 +0200 +++ /work/SRC/openSUSE:Factory/.orion.new/orion.changes 2017-10-17 01:53:57.670378785 +0200 @@ -1,0 +2,13 @@ +Mon Oct 09 13:30:14 UTC 2017 - pousadua...@gmail.com + +- Update to version 1.6.1+git~20171007: + * first cut of new chat replay + * avoid a loop requesting chat for the same timestamp if there's a problem + +------------------------------------------------------------------- +Fri Sep 22 12:24:28 UTC 2017 - pousadua...@gmail.com + +- Update to version 1.6.1+git~20170920: + * Fix desktop entry: don't hardcode icon path and correct category + +------------------------------------------------------------------- Old: ---- orion-1.6.1+git~20170827.tar.xz New: ---- orion-1.6.1+git~20171007.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ orion.spec ++++++ --- /var/tmp/diff_new_pack.2S2jLt/_old 2017-10-17 01:53:58.494340195 +0200 +++ /var/tmp/diff_new_pack.2S2jLt/_new 2017-10-17 01:53:58.498340007 +0200 @@ -17,7 +17,7 @@ Name: orion -Version: 1.6.1+git~20170827 +Version: 1.6.1+git~20171007 Release: 0 Summary: Twitch stream client using Qt License: GPL-3.0 @@ -55,10 +55,8 @@ sed -i 's|v$$VERSION|v%{version}-%{release}|g' orion.pro #fix paths sed -i 's|path = /usr/local/share/|path = /usr/share/|g' orion.pro -#update icon path in .desktop file -sed -i 's|Icon=/usr/local/share/icons/orion.svg|Icon=orion|g' distfiles/Orion.desktop #fix categories in .desktop file -sed -i 's|Categories=Games|Categories=Network;FileTransfer;|g' distfiles/Orion.desktop +sed -i 's|Categories=Game|Categories=Network;FileTransfer;|g' distfiles/Orion.desktop %build qmake-qt5 QMAKE_CFLAGS+="%optflags" QMAKE_CXXFLAGS+="%optflags" QMAKE_STRIP="/bin/true" @@ -80,13 +78,9 @@ %files %defattr(-,root,root) -#Fix SLE_12_SP2 and LEAP 42.1 builds -%if 0%{?sle_version} <= 120200 && 0%{?suse_version} <= 1320 -%doc README.md COPYING LICENSE.txt -%else %doc README.md %license COPYING LICENSE.txt -%endif + %attr(755,root,root) %{_bindir}/%{name} %{_datadir}/applications/Orion.desktop %{_datadir}/icons/hicolor/scalable/apps/%{name}.svg ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.2S2jLt/_old 2017-10-17 01:53:58.542337947 +0200 +++ /var/tmp/diff_new_pack.2S2jLt/_new 2017-10-17 01:53:58.542337947 +0200 @@ -1,4 +1,4 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/alamminsalo/orion.git</param> - <param name="changesrevision">594c474a49b16a8ab2baf019a6d24c8d5862316c</param></service></servicedata> \ No newline at end of file + <param name="changesrevision">0a5ccf34b68447648fb4fad5901bf1f47f89cc6e</param></service></servicedata> \ No newline at end of file ++++++ orion-1.6.1+git~20170827.tar.xz -> orion-1.6.1+git~20171007.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.6.1+git~20170827/distfiles/Orion.desktop new/orion-1.6.1+git~20171007/distfiles/Orion.desktop --- old/orion-1.6.1+git~20170827/distfiles/Orion.desktop 2017-08-27 10:29:37.000000000 +0200 +++ new/orion-1.6.1+git~20171007/distfiles/Orion.desktop 2017-10-07 17:56:22.000000000 +0200 @@ -2,9 +2,9 @@ Version=1.0 Type=Application Name=Orion -Icon=/usr/local/share/icons/orion.svg +Icon=orion Exec=/usr/bin/orion Comment=Seek and watch streams on Twitch -Categories=Games +Categories=Game Terminal=false StartupWMClass=orion diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.6.1+git~20170827/src/model/ircchat.cpp new/orion-1.6.1+git~20171007/src/model/ircchat.cpp --- old/orion-1.6.1+git~20170827/src/model/ircchat.cpp 2017-08-27 10:29:37.000000000 +0200 +++ new/orion-1.6.1+git~20171007/src/model/ircchat.cpp 2017-10-07 17:56:22.000000000 +0200 @@ -122,7 +122,6 @@ _bitsProvider = BadgeContainer::getInstance()->getBitsImageProvider(); connect(_badgeProvider, &ImageProvider::downloadComplete, this, &IrcChat::handleDownloadComplete); connect(_bitsProvider, &ImageProvider::downloadComplete, this, &IrcChat::handleDownloadComplete); - connect(VodManager::getInstance(), &VodManager::vodStartGetOperationFinished, this, &IrcChat::handleVodStartTime); connect(NetworkManager::getInstance(), &NetworkManager::vodChatPieceGetOperationFinished, this, &IrcChat::handleDownloadedReplayChat); connect(BadgeContainer::getInstance(), &BadgeContainer::channelBitsUrlsLoaded, this, &IrcChat::handleChannelBitsUrlsLoaded); connect(BadgeContainer::getInstance(), &BadgeContainer::channelBttvEmotesLoaded, this, &IrcChat::handleChannelBttvEmotesLoaded); @@ -187,18 +186,14 @@ replayVodId = vodId; - replayChatVodStartTime = vodStartEpochTime; + replayChatVodStartTime = 0; replayChatCurrentSeekOffset = playbackOffset; replayChatFirstLoadAfterSeek = true; // Get start timestamp of the chat replay segments as the requests need to be aligned to chunk boundaries from this start - NetworkManager::getInstance()->getVodStartTime(vodId); -} - -void IrcChat::handleVodStartTime(double startTime) { - replayChatFirstChunkTime = startTime; + replayChatFirstChunkTime = 0; replaySeek(replayChatCurrentSeekOffset); } @@ -207,8 +202,6 @@ return start + qRound(qFloor(rel / multiple) * multiple); } -const double CHAT_CHUNK_TIME = 30.0; - // When seeking, request past chat starting this far back in seconds... const double SEEK_HISTORY_TIME = 90.0; // ... and display up to this many messages of it. @@ -220,13 +213,13 @@ VodManager::getInstance()->cancelLastVodChatRequest(); } VodManager::getInstance()->resetVodChat(); + nextChatCursor.clear(); replayChatFirstLoadAfterSeek = true; replayChatRequestInProgress = true; // we save the offset to the point we want to start loading chat at as the current time replayChatCurrentTime = replayChatVodStartTime + qMax(newOffset - SEEK_HISTORY_TIME, 0.0); - qDebug() << "original vod playback start time" << QDateTime::fromMSecsSinceEpoch(replayChatCurrentTime * 1000.0, Qt::UTC); - nextChatChunkTimestamp = quantize(replayChatCurrentTime, replayChatFirstChunkTime, CHAT_CHUNK_TIME); - qDebug() << "quantized vod playback start time for chat chunk" << QDateTime::fromMSecsSinceEpoch(nextChatChunkTimestamp * 1000.0, Qt::UTC); + qDebug() << "original vod playback start time" << replayChatCurrentTime; + nextChatChunkTimestamp = replayChatCurrentTime; // we dump any pending messages from the previous playback position replayChatMessagesPending.clear(); // we'll do an initial request for starting offset chat right now. @@ -377,18 +370,32 @@ if (!replayChatRequestInProgress && needMoreChat) { replayChatRequestInProgress = true; - VodManager::getInstance()->getVodChatPiece(replayVodId, nextChatTime); + if (!nextChatCursor.isEmpty()) { + VodManager::getInstance()->getNextVodChatPiece(replayVodId, nextChatCursor); + } else { + qDebug() << "No cursor from last request; falling back to time-based request"; + VodManager::getInstance()->getVodChatPiece(replayVodId, nextChatChunkTimestamp); + } } } -void IrcChat::handleDownloadedReplayChat(QList<ReplayChatMessage> messages) { +void IrcChat::handleDownloadedReplayChat(ReplayChatPiece piece) { // eventually the initial chat response will arrive. we'll put the chat into a queue, and do a first chat update check + auto messages = piece.comments; replayChatMessagesPending.append(messages); + nextChatCursor = piece.next; - qDebug() << "CHAT REPLAY PART; t=" << QDateTime::fromMSecsSinceEpoch(nextChatChunkTimestamp * 1000.0, Qt::UTC) << messages.length() << "records"; - - nextChatChunkTimestamp += CHAT_CHUNK_TIME; + qDebug() << "CHAT REPLAY PART; t=" << nextChatChunkTimestamp << messages.length() << "records"; + + // next request will start at the end of the current request + if (!messages.empty()) { + nextChatChunkTimestamp = messages.last().videoOffset / 1000.0; + } else { + // make sure we're not just going to re-request the same timestamp if we fall back + // to a time-based request + nextChatChunkTimestamp += 60.0; + } replayChatRequestInProgress = false; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.6.1+git~20170827/src/model/ircchat.h new/orion-1.6.1+git~20171007/src/model/ircchat.h --- old/orion-1.6.1+git~20170827/src/model/ircchat.h 2017-08-27 10:29:37.000000000 +0200 +++ new/orion-1.6.1+git~20171007/src/model/ircchat.h 2017-10-07 17:56:22.000000000 +0200 @@ -144,8 +144,7 @@ void processError(QAbstractSocket::SocketError socketError); void processSslErrors(const QList<QSslError> &errors); void handleDownloadComplete(); - void handleVodStartTime(double); - void handleDownloadedReplayChat(QList<ReplayChatMessage>); + void handleDownloadedReplayChat(ReplayChatPiece); void handleChannelBitsUrlsLoaded(const int channelID, BitsQStringsMap bitsUrls); void blockedUsersLoaded(const QSet<QString> &); @@ -225,6 +224,7 @@ double replayChatCurrentSeekOffset; double replayChatCurrentTime; // the position that playback is currently at in chat double nextChatChunkTimestamp; + QString nextChatCursor; quint64 replayVodId; QList<ReplayChatMessage> replayChatMessagesPending; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.6.1+git~20170827/src/model/vodmanager.cpp new/orion-1.6.1+git~20171007/src/model/vodmanager.cpp --- old/orion-1.6.1+git~20170827/src/model/vodmanager.cpp 2017-08-27 10:29:37.000000000 +0200 +++ new/orion-1.6.1+git~20171007/src/model/vodmanager.cpp 2017-10-07 17:56:22.000000000 +0200 @@ -24,7 +24,6 @@ connect(netman, &NetworkManager::broadcastsOperationFinished, this, &VodManager::onSearchFinished); connect(netman, &NetworkManager::m3u8OperationBFinished, this, &VodManager::streamsGetFinished); - connect(netman, &NetworkManager::vodStartGetOperationFinished, this, &VodManager::vodStartGetOperationFinished); connect(netman, &NetworkManager::vodChatPieceGetOperationFinished, this, &VodManager::vodChatPieceGetOperationFinished); QSettings settings("orion.application", "Orion"); @@ -119,14 +118,14 @@ netman->getBroadcastPlaybackStream(vod); } -void VodManager::getVodStartTime(quint64 vodId) { - netman->getVodStartTime(vodId); -} - void VodManager::getVodChatPiece(quint64 vodId, quint64 offset) { netman->getVodChatPiece(vodId, offset); } +void VodManager::getNextVodChatPiece(quint64 vodId, QString cursor) { + netman->getNextVodChatPiece(vodId, cursor); +} + void VodManager::cancelLastVodChatRequest() { netman->cancelLastVodChatRequest(); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.6.1+git~20170827/src/model/vodmanager.h new/orion-1.6.1+git~20171007/src/model/vodmanager.h --- old/orion-1.6.1+git~20170827/src/model/vodmanager.h 2017-08-27 10:29:37.000000000 +0200 +++ new/orion-1.6.1+git~20171007/src/model/vodmanager.h 2017-10-07 17:56:22.000000000 +0200 @@ -55,8 +55,8 @@ void onSearchFinished(QList<Vod *>); void cancelLastVodChatRequest(); void resetVodChat(); - void getVodStartTime(quint64 vodId); void getVodChatPiece(quint64 vodId, quint64 offset); + void getNextVodChatPiece(quint64 vodId, QString cursor); void setVodLastPlaybackPosition(const QString & channel, const QString & vod, quint64 position); QVariant getVodLastPlaybackPosition(const QString & channel, const QString & vod); QVariantMap getChannelVodsLastPlaybackPositions(const QString & channel); @@ -68,8 +68,7 @@ void searchFinished(); void streamsGetFinished(QVariantMap items); - void vodStartGetOperationFinished(double); - void vodChatPieceGetOperationFinished(QList<ReplayChatMessage>); + void vodChatPieceGetOperationFinished(ReplayChatPiece); void vodLastPositionUpdated(const QString & channel, const QString & vod, const quint64 position); private: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.6.1+git~20170827/src/network/networkmanager.cpp new/orion-1.6.1+git~20171007/src/network/networkmanager.cpp --- old/orion-1.6.1+git~20170827/src/network/networkmanager.cpp 2017-08-27 10:29:37.000000000 +0200 +++ new/orion-1.6.1+git~20171007/src/network/networkmanager.cpp 2017-10-07 17:56:22.000000000 +0200 @@ -410,21 +410,6 @@ connect(reply, &QNetworkReply::finished, this, &NetworkManager::emoteSetsReply); } -void NetworkManager::getVodStartTime(quint64 vodId) { - QString url = QString(TWITCH_RECHAT_API) + QString("?start=0&video_id=v%1").arg(vodId); - - qDebug() << "Failing request to get offset"; - qDebug() << "Request" << url; - - QNetworkRequest request; - request.setUrl(url); - - QNetworkReply *reply = operation->get(request); - - connect(reply, &QNetworkReply::finished, this, &NetworkManager::vodStartReply); - -} - void NetworkManager::loadChatterList(const QString channel) { qDebug() << "Loading viewer list for" << channel; const QString url = QString(TWITCH_TMI_USER_API) + channel + QString("/chatters"); @@ -594,11 +579,13 @@ } void NetworkManager::getVodChatPiece(quint64 vodId, quint64 offset) { - QString url = QString(TWITCH_RECHAT_API) + QString("?start=%1&video_id=v%2").arg(offset).arg(vodId); + QString url = QString(TWITCH_API_V5) + QString("/videos/%2/comments?content_offset_seconds=%1").arg(offset).arg(vodId); qDebug() << "Requesting" << url; QNetworkRequest request; + request.setRawHeader("Accept", "application/vnd.twitchtv.v5+json"); + request.setRawHeader("Client-ID", getClientId().toUtf8()); request.setUrl(url); QNetworkReply *reply = operation->get(request); @@ -608,6 +595,23 @@ connect(reply, &QNetworkReply::finished, this, &NetworkManager::vodChatPieceReply); } +void NetworkManager::getNextVodChatPiece(quint64 vodId, QString cursor) { + QString url = QString(TWITCH_API_V5) + QString("/videos/%2/comments?cursor=%1").arg(cursor).arg(vodId); + + qDebug() << "Requesting" << url; + + QNetworkRequest request; + request.setRawHeader("Accept", "application/vnd.twitchtv.v5+json"); + request.setRawHeader("Client-ID", getClientId().toUtf8()); + request.setUrl(url); + + QNetworkReply *reply = operation->get(request); + + lastVodChatRequest = reply; + + connect(reply, &QNetworkReply::finished, this, &NetworkManager::vodChatPieceReply); +} + void NetworkManager::cancelLastVodChatRequest() { if (lastVodChatRequest != nullptr) { lastVodChatRequest->abort(); @@ -654,55 +658,6 @@ replayChatPartNum++; } -void NetworkManager::vodStartReply() { - QNetworkReply* reply = qobject_cast<QNetworkReply *>(sender()); - - if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) != 400) { - if (!handleNetworkError(reply)) { - return; - } - } - - QByteArray data = reply->readAll(); - - //qDebug() << data; - - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(data, &error); - - double startTime = 0.0; - - if (error.error == QJsonParseError::NoError) { - QJsonObject json = doc.object(); - - //error_text = d['errors'][0]['detail'] - QString chatReplayErrorMessage = json["errors"].toArray()[0].toObject()["detail"].toString(); - // "not between (\d +) and (\d+) - - const QString START_MARKER = "not between "; - - int startMarkerPos = chatReplayErrorMessage.indexOf(START_MARKER); - if (startMarkerPos == -1) { - qDebug() << "chat replay error message in unexpected format:" << chatReplayErrorMessage; - return; - } - - int timePos = startMarkerPos + START_MARKER.length(); - QString timeStr = chatReplayErrorMessage.mid(timePos); - int timeEnd = timeStr.indexOf(' '); - if (timeEnd != -1) { - timeStr = timeStr.left(timeEnd); - } - qDebug() << "timeStr" << timeStr; - startTime = timeStr.toDouble(); - } - - emit vodStartGetOperationFinished(startTime); - - reply->deleteLater(); - -} - void NetworkManager::vodChatPieceReply() { QNetworkReply* reply = qobject_cast<QNetworkReply *>(sender()); @@ -718,9 +673,9 @@ //qDebug() << data; - QList<ReplayChatMessage> ret = JsonParser::parseVodChatPiece(data); + ReplayChatPiece ret = JsonParser::parseVodChatPiece(data); - filterReplayChat(ret); + filterReplayChat(ret.comments); emit vodChatPieceGetOperationFinished(ret); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.6.1+git~20170827/src/network/networkmanager.h new/orion-1.6.1+git~20171007/src/network/networkmanager.h --- old/orion-1.6.1+git~20170827/src/network/networkmanager.h 2017-08-27 10:29:37.000000000 +0200 +++ new/orion-1.6.1+git~20171007/src/network/networkmanager.h 2017-10-07 17:56:22.000000000 +0200 @@ -97,8 +97,8 @@ void getChannelBttvEmotes(const QString channel); void getGlobalBttvEmotes(); - Q_INVOKABLE void getVodStartTime(quint64 vodId); Q_INVOKABLE void getVodChatPiece(quint64 vodId, quint64 offset); + Q_INVOKABLE void getNextVodChatPiece(quint64 vodId, QString cursor); Q_INVOKABLE void cancelLastVodChatRequest(); Q_INVOKABLE void resetVodChat(); Q_INVOKABLE void loadChatterList(const QString channel); @@ -136,8 +136,7 @@ void getChannelBadgeBetaUrlsOperationFinished(const int, const QMap<QString, QMap<QString, QMap<QString, QString>>>); void getGlobalBadgeBetaUrlsOperationFinished(const QMap<QString, QMap<QString, QMap<QString, QString>>>); - void vodStartGetOperationFinished(double); - void vodChatPieceGetOperationFinished(QList<ReplayChatMessage>); + void vodChatPieceGetOperationFinished(ReplayChatPiece); void chatterListLoadOperationFinished(QMap<QString, QList<QString>>); void blockedUserListLoadOperationFinished(QList<QString>, const quint32 nextOffset, const quint32 total); @@ -174,7 +173,6 @@ void favouritesReply(); void editUserFavouritesReply(); void streamReply(); - void vodStartReply(); void vodChatPieceReply(); void chatterListReply(); void blockedUserListReply(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.6.1+git~20170827/src/network/replaychat.h new/orion-1.6.1+git~20171007/src/network/replaychat.h --- old/orion-1.6.1+git~20170827/src/network/replaychat.h 2017-08-27 10:29:37.000000000 +0200 +++ new/orion-1.6.1+git~20171007/src/network/replaychat.h 2017-10-07 17:56:22.000000000 +0200 @@ -29,4 +29,10 @@ QList<int> emoteList; }; -#endif \ No newline at end of file +struct ReplayChatPiece { + QList<ReplayChatMessage> comments; + QString next; + QString prev; +}; + +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.6.1+git~20170827/src/network/urls.h new/orion-1.6.1+git~20171007/src/network/urls.h --- old/orion-1.6.1+git~20170827/src/network/urls.h 2017-08-27 10:29:37.000000000 +0200 +++ new/orion-1.6.1+git~20171007/src/network/urls.h 2017-10-07 17:56:22.000000000 +0200 @@ -17,10 +17,10 @@ #define KRAKEN_API "https://api.twitch.tv/kraken" #define TWITCH_API "https://api.twitch.tv/api" -#define TWITCH_RECHAT_API "https://rechat.twitch.tv/rechat-messages" #define TWITCH_TMI_USER_API "https://tmi.twitch.tv/group/user/" //#define TWITCH_EMOTES "http://static-cdn.jtvnw.net/emoticons/v1/" #define BTTV_API "https://api.betterttv.net/2" +#define TWITCH_API_V5 "https://api.twitch.tv/v5" #define CLIENT_ID "0dpzlnp1w2bjlim3ldp0u96o4dq2gm" #endif // URLS_H diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.6.1+git~20170827/src/util/jsonparser.cpp new/orion-1.6.1+git~20171007/src/util/jsonparser.cpp --- old/orion-1.6.1+git~20170827/src/util/jsonparser.cpp 2017-08-27 10:29:37.000000000 +0200 +++ new/orion-1.6.1+git~20171007/src/util/jsonparser.cpp 2017-10-07 17:56:22.000000000 +0200 @@ -609,72 +609,138 @@ return total; } +int unicodeLen(const QString & text) { + int out = 0; + for (auto i = 0; i < text.length(); i++) { + auto ch = text.at(i); + if ((ch < 0xd800) || (ch > 0xdbff)) { + ++out; + } + } + return out; +} + ReplayChatMessage parseVodChatEntry(const QJsonValue &entry) { ReplayChatMessage out; - const QJsonObject & entryObj = entry.toObject(); + const QJsonObject & attributes = entry.toObject(); - const QJsonObject & attributes = entryObj["attributes"].toObject(); + auto msgId = attributes["_id"].toString(); + out.id = msgId; - out.from = attributes["from"].toString(); - out.deleted = attributes["deleted"].toBool(); - out.message = attributes["message"].toString(); - out.room = attributes["room"].toString(); - out.timestamp = attributes["timestamp"].toDouble(); - out.videoOffset = attributes["video-offset"].toDouble(); - out.command = attributes["command"].toString(); - - auto tags = attributes["tags"].toObject(); - for (auto tagEntry = tags.constBegin(); tagEntry != tags.constEnd(); tagEntry++) { - auto tagName = tagEntry.key(); - if (tagName == "emotes") { - auto emotes = tagEntry.value().toObject(); - for (auto emoteEntry = emotes.constBegin(); emoteEntry != emotes.constEnd(); emoteEntry++) { - int emoteId = emoteEntry.key().toInt(); + auto commenter = attributes["commenter"].toObject(); - out.emoteList.append(emoteId); + auto name = commenter["name"].toString(); + out.from = name; + QString state = attributes["state"].toString(); + bool deleted = state != QString("published"); + out.deleted = deleted; // XXX other types to take as valid? + + auto message = attributes["message"].toObject(); + out.message = message["body"].toString(); + auto channelId = attributes["channel_id"].toString(); + out.room = channelId; + out.videoOffset = attributes["content_offset_seconds"].toDouble() * 1000.0; + out.timestamp = out.videoOffset; + + auto source = attributes["source"].toString(); + if (source == QString("chat")) { + out.command = "PRIVMSG"; + } + else { + // XXX need some more guesses for these + qDebug() << "unknown message source" << source; // XXX remove + out.command = "PRIVMSG"; + } + + int unicodePos = 0; + auto fragments = message["fragments"].toArray(); - auto emotePairs = emoteEntry.value().toArray(); - for (auto emotePair : emotePairs) { - auto emotePairArray = emotePair.toArray(); - if (emotePairArray.size() == 2) { - auto first = emotePairArray[0].toInt(); - auto last = emotePairArray[1].toInt(); - out.emotePositionsMap.insert(first, qMakePair(last, emoteId)); - } - } + QSet<int> emoteIdsSeen; + + for (const auto & fragment : fragments) { + auto fragmentObj = fragment.toObject(); + auto text = fragmentObj["text"].toString(); + + auto curTextUnicodeLen = unicodeLen(text); + + if (!fragmentObj["emoticon"].isNull()) { + auto emoteObj = fragmentObj["emoticon"].toObject(); + + QString emotioconIDStr = emoteObj["emoticon_id"].toString(); + int first = unicodePos; + int last = unicodePos + curTextUnicodeLen - 1; // XXX off by one? + int emoteId = emotioconIDStr.toInt(); + if (emoteIdsSeen.constFind(emoteId) == emoteIdsSeen.constEnd()) { + emoteIdsSeen.insert(emoteId); + out.emoteList.append(emoteId); } + out.emotePositionsMap.insert(first, qMakePair(last, emoteId)); + } + + unicodePos += curTextUnicodeLen; + + } + + // XXX tags collection stuff not hooked up yet + // system-msg + // @badges=staff/1,broadcaster/1,turbo/1;color=#008000;display-name=TWITCH_UserName;emotes=;mod=0;msg-id=resub;msg-param-months=6;room-id=1337;subscriber=1;system-msg=TWITCH_UserName\shas\ssubscribed\sfor\s6\smonths!;login=twitch_username;turbo=1;user-id=1337;user-type=staff :tmi.twitch.tv USERNOTICE #channel :Great stream -- keep it up! + QList<QString> badges; + for (const auto & badge : message["user_badges"].toArray()) { + auto badgeObj = badge.toObject(); + auto badgeId = badgeObj["_id"].toString(); + badges.append(badgeId + QString("/") + badgeObj["version"].toString()); + + // not sure if anything in the front end needs these as tags if we added the badges directly + if (badgeId == QString("moderator")) { + out.tags.insert("mod", true); } - else if (tagName == "mod" || tagName == "subscriber" || tagName == "turbo") { - out.tags.insert(tagName, tagEntry.value().toBool()); + + if (badgeId == QString("subscriber")) { + out.tags.insert("subscriber", true); } - else { - out.tags.insert(tagName, tagEntry.value().toString()); + + if (badgeId == QString("turbo")) { + out.tags.insert("turbo", true); } } - out.id = entryObj["id"].toString(); + if (!badges.isEmpty()) { + out.tags.insert("badges", badges.join(",")); + } + + // most of this stuff is being populated for backward compatibility with things that currently operate on the tags + // and can be taken out if nothing uses it + out.tags.insert("msg-id", msgId); + out.tags.insert("display-name", commenter["display_name"].toString()); + out.tags.insert("login", name); + out.tags.insert("color", message["user_color"].toString()); + out.tags.insert("room-id", channelId); + out.tags.insert("user-id", commenter["_id"].toString()); + out.tags.insert("user-type", commenter["type"].toString()); return out; } -QList<ReplayChatMessage> JsonParser::parseVodChatPiece(const QByteArray &data) +ReplayChatPiece JsonParser::parseVodChatPiece(const QByteArray &data) { - QList<ReplayChatMessage> out; + ReplayChatPiece out; QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(data, &error); if (error.error == QJsonParseError::NoError) { QJsonObject json = doc.object(); - if (!json["data"].isNull() && json["data"].isArray()) { - const QJsonArray & chatEntries = json["data"].toArray(); + if (!json["comments"].isNull() && json["comments"].isArray()) { + const QJsonArray & chatEntries = json["comments"].toArray(); for (const auto & entry : chatEntries) { - - out.append(parseVodChatEntry(entry)); + out.comments.append(parseVodChatEntry(entry)); } } + + out.next = json["_next"].toString(); + out.prev = json["_prev"].toString(); } return out; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.6.1+git~20170827/src/util/jsonparser.h new/orion-1.6.1+git~20171007/src/util/jsonparser.h --- old/orion-1.6.1+git~20170827/src/util/jsonparser.h 2017-08-27 10:29:37.000000000 +0200 +++ new/orion-1.6.1+git~20171007/src/util/jsonparser.h 2017-10-07 17:56:22.000000000 +0200 @@ -60,7 +60,7 @@ static QMap<int, QMap<int, QString>> parseEmoteSets(const QByteArray&); static QMap<QString, QMap<QString, QString>> parseChannelBadgeUrls(const QByteArray &data); static QMap<QString, QMap<QString, QMap<QString, QString>>> parseBadgeUrlsBetaFormat(const QByteArray &data); - static QList<ReplayChatMessage> parseVodChatPiece(const QByteArray &data); + static ReplayChatPiece parseVodChatPiece(const QByteArray &data); static QMap<QString, QList<QString>> parseChatterList(const QByteArray &data); static PagedResult<QString> parseBlockList(const QByteArray &data); static void parseBitsData(const QByteArray &data, QMap<QString, QMap<QString, QString>> & outUrls, QMap<QString, QMap<QString, QString>> & outColors);