Hello community, here is the log from the commit of package orion for openSUSE:Factory checked in at 2017-06-04 02:01:28 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/orion (Old) and /work/SRC/openSUSE:Factory/.orion.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "orion" Sun Jun 4 02:01:28 2017 rev:6 rq:500859 version:1.5.1+git~20170602 Changes: -------- --- /work/SRC/openSUSE:Factory/orion/orion.changes 2017-05-31 12:19:23.295398204 +0200 +++ /work/SRC/openSUSE:Factory/.orion.new/orion.changes 2017-06-04 02:01:37.648969159 +0200 @@ -1,0 +2,15 @@ +Fri Jun 02 09:05:56 UTC 2017 - [email protected] + +- Update to version 1.5.1+git~20170602: + * collect _total from responses to be shown in search view; stop cascading requests from responses once the total from the previous response is reached (#171) + * BTTV Emote Support (#168) + * when watching a VOD with chat replay, show a more consistent number of previous messages after seeking (#169) + +------------------------------------------------------------------- +Thu Jun 01 16:03:31 UTC 2017 - [email protected] + +- Update to version 1.5.1+git~20170601: + * switch followed and blocked paged results handling to use JSON _total and not stop immediately after a non-full page (#166) +- Include fontawesome-fonts as dependency to fix GUI issues + +------------------------------------------------------------------- Old: ---- orion-1.5.1+git~20170519.tar.xz New: ---- orion-1.5.1+git~20170602.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ orion.spec ++++++ --- /var/tmp/diff_new_pack.tK3Gdm/_old 2017-06-04 02:01:39.580696498 +0200 +++ /var/tmp/diff_new_pack.tK3Gdm/_new 2017-06-04 02:01:39.580696498 +0200 @@ -17,7 +17,7 @@ Name: orion -Version: 1.5.1+git~20170519 +Version: 1.5.1+git~20170602 Release: 0 Summary: Twitch stream client using Qt License: GPL-3.0 @@ -33,9 +33,12 @@ BuildRequires: pkgconfig(Qt5Svg) >= 5.6 BuildRequires: pkgconfig(mpv) -#Required for GUI to function, It is not added automatically by OBS (probably due to QML?) +#Required for GUI to function, it is not added automatically by OBS (probably due to QML?) Requires: libqt5-qtquickcontrols2 +#Required to display GUI icons properly +Requires: fontawesome-fonts + Requires(post): hicolor-icon-theme Requires(postun): hicolor-icon-theme Requires(post): update-desktop-files ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.tK3Gdm/_old 2017-06-04 02:01:39.624690288 +0200 +++ /var/tmp/diff_new_pack.tK3Gdm/_new 2017-06-04 02:01:39.624690288 +0200 @@ -1,4 +1,4 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/alamminsalo/orion.git</param> - <param name="changesrevision">0f79a6bb5132c97ba1602b59596eeb669a4977f0</param></service></servicedata> \ No newline at end of file + <param name="changesrevision">fe4787b43bf32c4795031965fade34e1f797f7f2</param></service></servicedata> \ No newline at end of file ++++++ orion-1.5.1+git~20170519.tar.xz -> orion-1.5.1+git~20170602.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/model/channelmanager.cpp new/orion-1.5.1+git~20170602/src/model/channelmanager.cpp --- old/orion-1.5.1+git~20170519/src/model/channelmanager.cpp 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/model/channelmanager.cpp 2017-06-02 07:42:07.000000000 +0200 @@ -188,6 +188,9 @@ connect(netman, &NetworkManager::getChannelBitsUrlsOperationFinished, this, &ChannelManager::innerChannelBitsDataLoaded); connect(netman, &NetworkManager::getGlobalBitsUrlsOperationFinished, this, &ChannelManager::innerGlobalBitsDataLoaded); + connect(netman, &NetworkManager::getGlobalBttvEmotesOperationFinished, this, &ChannelManager::innerGlobalBttvEmotesLoaded); + connect(netman, &NetworkManager::getChannelBttvEmotesOperationFinished, this, &ChannelManager::innerChannelBttvEmotesLoaded); + connect(netman, &NetworkManager::favouritesReplyFinished, this, &ChannelManager::addFollowedResults); connect(netman, &NetworkManager::vodStartGetOperationFinished, this, &ChannelManager::vodStartGetOperationFinished); connect(netman, &NetworkManager::vodChatPieceGetOperationFinished, this, &ChannelManager::vodChatPieceGetOperationFinished); @@ -581,7 +584,7 @@ emit searchingStarted(); } -void ChannelManager::addSearchResults(const QList<Channel*> &list) +void ChannelManager::addSearchResults(const QList<Channel*> &list, const int total) { bool needsStreamCheck = false; @@ -600,7 +603,7 @@ qDeleteAll(list); - emit resultsUpdated(numAdded); + emit resultsUpdated(numAdded, total); } void ChannelManager::getFeatured() @@ -818,6 +821,32 @@ return out; } +bool ChannelManager::loadChannelBttvEmotes(const QString channel) { + bool out = false; + + auto result = channelBttvEmotes.constFind(channel); + if (result != channelBttvEmotes.constEnd()) { + // deliver cached channel bttv emotes + emit channelBttvEmotesLoaded(channel, result.value()); + } + else { + netman->getChannelBttvEmotes(channel); + out = true; + } + + const QString GLOBAL_EMOTES_IDENTIFIER = "GLOBAL"; + result = channelBttvEmotes.constFind(GLOBAL_EMOTES_IDENTIFIER); + if (result != channelBttvEmotes.constEnd()) { + emit channelBttvEmotesLoaded(GLOBAL_EMOTES_IDENTIFIER, result.value()); + } + else { + netman->getGlobalBttvEmotes(); + out = true; + } + + return out; +} + /* QVariantMap convertBitsUrls(const QMap<QString, QMap<QString, QString>> & bitsUrls) { QVariantMap out; @@ -944,6 +973,19 @@ emit channelBitsUrlsLoaded(GLOBAL_BITS_KEY, globalBitsUrls, globalBitsColors); } +void ChannelManager::innerChannelBttvEmotesLoaded(const QString channel, QMap<QString, QString> & emotesByCode) { + channelBttvEmotes.remove(channel); + channelBttvEmotes.insert(channel, emotesByCode); + emit channelBttvEmotesLoaded(channel, emotesByCode); +} + +void ChannelManager::innerGlobalBttvEmotesLoaded(QMap<QString, QString> & emotesByCode) { + const QString GLOBAL_EMOTES_KEY = "GLOBAL"; + channelBttvEmotes.remove(GLOBAL_EMOTES_KEY); + channelBttvEmotes.insert(GLOBAL_EMOTES_KEY, emotesByCode); + emit channelBttvEmotesLoaded(GLOBAL_EMOTES_KEY, emotesByCode); +} + void ChannelManager::getFollowedChannels(const quint32& limit, const quint32& offset) { //if (offset == 0) @@ -953,14 +995,14 @@ } -void ChannelManager::addFollowedResults(const QList<Channel *> &list, const quint32 offset) +void ChannelManager::addFollowedResults(const QList<Channel *> &list, const quint32 offset, const quint32 total) { // qDebug() << "Merging channel data for " << list.size() // << " items with " << offset << " offset."; favouritesModel->mergeAll(list); - if (list.size() == FOLLOWED_FETCH_LIMIT) + if (offset < total) getFollowedChannels(FOLLOWED_FETCH_LIMIT, offset); checkStreams(list); @@ -978,13 +1020,13 @@ netman->getBlockedUserList(accessToken(), user_id, 0, BLOCKED_USER_LIST_FETCH_LIMIT); } -void ChannelManager::addBlockedUserResults(const QList<QString> & list, const quint32 nextOffset) +void ChannelManager::addBlockedUserResults(const QList<QString> & list, const quint32 nextOffset, const quint32 total) { if (!user_id || accessToken().isEmpty()) return; blockedUserListLoading.append(list); - if (list.size() == BLOCKED_USER_LIST_FETCH_LIMIT) { + if (nextOffset < total) { netman->getBlockedUserList(accessToken(), user_id, nextOffset, BLOCKED_USER_LIST_FETCH_LIMIT); } else { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/model/channelmanager.h new/orion-1.5.1+git~20170602/src/model/channelmanager.h --- old/orion-1.5.1+git~20170519/src/model/channelmanager.h 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/model/channelmanager.h 2017-06-02 07:42:07.000000000 +0200 @@ -114,6 +114,7 @@ QMap<int, QMap<int, QString>> lastEmoteSets; QMap<QString, QMap<QString, QMap<QString, QString>>> channelBadgeUrls; QMap<QString, QMap<QString, QMap<QString, QMap<QString, QString>>>> channelBadgeBetaUrls; + QMap<QString, QMap<QString, QString>> channelBttvEmotes; QMap<int, QMap<QString, QMap<QString, QString>>> channelBitsUrls; QMap<int, QMap<QString, QMap<QString, QString>>> channelBitsColors; @@ -179,6 +180,8 @@ Q_INVOKABLE bool loadChannelBitsUrls(int channel); Q_INVOKABLE void loadChatterList(const QString channel); + Q_INVOKABLE bool loadChannelBttvEmotes(const QString channel); + Q_INVOKABLE void cancelLastVodChatRequest(); Q_INVOKABLE void resetVodChat(); Q_INVOKABLE void getVodStartTime(quint64 vodId); @@ -269,7 +272,7 @@ signals: void pushNotification(const QString &title, const QString &message, const QString &imgUrl); - void resultsUpdated(int numAdded); + void resultsUpdated(int numAdded, int total); void featuredUpdated(); void searchingStarted(); void foundPlaybackStream(const QVariantMap &streams); @@ -292,6 +295,8 @@ void channelBitsUrlsLoaded(const int channelID, BitsQStringsMap bitsUrls, BitsQStringsMap bitsColors); + void channelBttvEmotesLoaded(const QString channel, QMap<QString, QString> emotesByCode); + void vodStartGetOperationFinished(double); void vodChatPieceGetOperationFinished(QList<ReplayChatMessage>); @@ -316,7 +321,7 @@ void setAccessToken(const QString &arg); private slots: - void addSearchResults(const QList<Channel*>&); + void addSearchResults(const QList<Channel*>&, const int total); void addFeaturedResults(const QList<Channel*>&); void updateFavourites(const QList<Channel*>&); void updateStreams(const QList<Channel*>&); @@ -328,10 +333,12 @@ void innerGlobalBadgeBetaUrlsLoaded(const QMap<QString, QMap<QString, QMap<QString, QString>>> badgeData); void innerChannelBitsDataLoaded(int channelID, BitsQStringsMap channelBitsUrls, BitsQStringsMap channelBitsColors); void innerGlobalBitsDataLoaded(BitsQStringsMap globalBitsUrls, BitsQStringsMap globalBitsColors); - void addFollowedResults(const QList<Channel*>&, const quint32); + void innerChannelBttvEmotesLoaded(const QString channel, QMap<QString, QString> & emotesByCode); + void innerGlobalBttvEmotesLoaded(QMap<QString, QString> & emotesByCode); + void addFollowedResults(const QList<Channel*>&, const quint32, const quint32); void onNetworkAccessChanged(bool); void processChatterList(QMap<QString, QList<QString>> chatters); - void addBlockedUserResults(const QList<QString> & list, const quint32 nextOffset); + void addBlockedUserResults(const QList<QString> & list, const quint32 nextOffset, const quint32 total); void innerUserBlocked(quint64 myUserId, const QString & blockedUsername); void innerUserUnblocked(quint64 myUserId, const QString & unblockedUsername); }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/model/imageprovider.cpp new/orion-1.5.1+git~20170602/src/model/imageprovider.cpp --- old/orion-1.5.1+git~20170519/src/model/imageprovider.cpp 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/model/imageprovider.cpp 2017-06-02 07:42:07.000000000 +0200 @@ -163,7 +163,7 @@ return new CachedImageProvider(_imageTable); } -bool ImageProvider::downloadsInProgress() { +bool ImageProvider::downloadsInProgress() const { return activeDownloadCount > 0; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/model/imageprovider.h new/orion-1.5.1+git~20170602/src/model/imageprovider.h --- old/orion-1.5.1+git~20170519/src/model/imageprovider.h 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/model/imageprovider.h 2017-06-02 07:42:07.000000000 +0200 @@ -70,7 +70,7 @@ * Returns true if caller should wait for a downloadComplete event before using the emote */ bool makeAvailable(QString key); bool download(QString key); - bool downloadsInProgress(); + bool downloadsInProgress() const; QHash<QString, QImage*> imageTable(); void loadImageFile(QString key, QString filename); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/model/ircchat.cpp new/orion-1.5.1+git~20170602/src/model/ircchat.cpp --- old/orion-1.5.1+git~20170519/src/model/ircchat.cpp 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/model/ircchat.cpp 2017-06-02 07:42:07.000000000 +0200 @@ -37,6 +37,9 @@ const QString IrcChat::IMAGE_PROVIDER_BITS = "bits"; const QString IrcChat::EMOTICONS_URL_FORMAT_LODPI = "https://static-cdn.jtvnw.net/emoticons/v1/%1/1.0"; const QString IrcChat::EMOTICONS_URL_FORMAT_HIDPI = "https://static-cdn.jtvnw.net/emoticons/v1/%1/2.0"; +const QString IrcChat::IMAGE_PROVIDER_BTTV_EMOTE = "bttvemote"; +const QString IrcChat::BTTV_EMOTES_URL_FORMAT_LODPI = "https://cdn.betterttv.net/emote/%1/1x"; +const QString IrcChat::BTTV_EMOTES_URL_FORMAT_HIDPI = "https://cdn.betterttv.net/emote/%1/2x"; const qint16 IrcChat::PORT = 443; const QString IrcChat::HOST = "irc.chat.twitch.tv"; @@ -55,6 +58,7 @@ IrcChat::IrcChat(QObject *parent) : QObject(parent), _emoteProvider(IMAGE_PROVIDER_EMOTE, hiDpi? EMOTICONS_URL_FORMAT_HIDPI : EMOTICONS_URL_FORMAT_LODPI, ".png", hiDpi? "emotes_2x" : "emotes"), + _bttvEmoteProvider(IMAGE_PROVIDER_BTTV_EMOTE, hiDpi? BTTV_EMOTES_URL_FORMAT_HIDPI : BTTV_EMOTES_URL_FORMAT_LODPI, ".png", hiDpi? "bttv_emotes_2x" : "bttv_emotes"), _bitsProvider(nullptr), _badgeProvider(nullptr), _cman(nullptr), @@ -64,8 +68,11 @@ logged_in = false; - connect(&_emoteProvider, &ImageProvider::downloadComplete, this, &IrcChat::handleDownloadComplete); - connect(&_emoteProvider, &ImageProvider::bulkDownloadComplete, this, &IrcChat::bulkDownloadComplete); + + for (const auto provider : { &_emoteProvider, &_bttvEmoteProvider }) { + connect(provider, &ImageProvider::downloadComplete, this, &IrcChat::handleDownloadComplete); + connect(provider, &ImageProvider::bulkDownloadComplete, this, &IrcChat::bulkDownloadComplete); + } room = ""; @@ -97,6 +104,7 @@ void IrcChat::RegisterEngineProviders(QQmlEngine & engine) { engine.addImageProvider(IMAGE_PROVIDER_EMOTE, _emoteProvider.getQMLImageProvider()); + engine.addImageProvider(IMAGE_PROVIDER_BTTV_EMOTE, _bttvEmoteProvider.getQMLImageProvider()); if (_badgeProvider) { engine.addImageProvider(_badgeProvider->getImageProviderName(), _badgeProvider->getQMLImageProvider()); } @@ -118,6 +126,7 @@ connect(_cman, &ChannelManager::vodStartGetOperationFinished, this, &IrcChat::handleVodStartTime); connect(_cman, &ChannelManager::vodChatPieceGetOperationFinished, this, &IrcChat::handleDownloadedReplayChat); connect(_cman, &ChannelManager::channelBitsUrlsLoaded, this, &IrcChat::handleChannelBitsUrlsLoaded); + connect(_cman, &ChannelManager::channelBttvEmotesLoaded, this, &IrcChat::handleChannelBttvEmotesLoaded); connect(_cman, &ChannelManager::blockedUsersLoaded, this, &IrcChat::blockedUsersLoaded); connect(_cman, &ChannelManager::userBlocked, this, &IrcChat::userBlocked); connect(_cman, &ChannelManager::userUnblocked, this, &IrcChat::userUnblocked); @@ -127,6 +136,13 @@ } } +bool IrcChat::allDownloadsComplete() { + return !_emoteProvider.downloadsInProgress() && + !_bttvEmoteProvider.downloadsInProgress() && + _badgeProvider != nullptr && !_badgeProvider->downloadsInProgress() && + _bitsProvider != nullptr && !_bitsProvider->downloadsInProgress(); +} + IrcChat::~IrcChat() { disconnect(); } @@ -149,6 +165,8 @@ if (_bitsProvider) { _bitsProvider->setChannelId(channelId.toInt()); } + + lastCurChannelBttvEmoteFixedStrings.clear(); } void IrcChat::join(const QString channel, const QString channelId) { @@ -178,6 +196,8 @@ 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 _cman->getVodStartTime(vodId); } @@ -194,19 +214,25 @@ 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. +const int SEEK_HISTORY_MESSAGE_LIMIT = 15; + void IrcChat::replaySeek(double newOffset) { // we set a flag indicating that a request is in flight if (replayChatRequestInProgress) { _cman->cancelLastVodChatRequest(); } _cman->resetVodChat(); + replayChatFirstLoadAfterSeek = true; replayChatRequestInProgress = true; - // we save the offset as the current time - replayChatCurrentTime = replayChatVodStartTime + newOffset; + // 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); - // we dump any pending messages from other stuff + // we dump any pending messages from the previous playback position replayChatMessagesPending.clear(); // we'll do an initial request for starting offset chat right now. _cman->getVodChatPiece(replayVodId, nextChatChunkTimestamp); @@ -309,29 +335,55 @@ double curVideoOffsetMS = (replayChatCurrentTime - replayChatVodStartTime) * 1000.0; - // in a chat update check, we look at the current time emit whatever chat's timestamps are up - while (!replayChatMessagesPending.empty() && (replayChatMessagesPending.first().videoOffset) <= curVideoOffsetMS) { - auto message = replayChatMessagesPending.first(); - auto delay = (curVideoOffsetMS - (replayChatMessagesPending.first().videoOffset)) / 1000.0; + double nextChatTime = nextChatChunkTimestamp; + // if we're close enough to the end of the chat buffer, we need more chat right away + bool needMoreChat = replayChatCurrentTime > (nextChatTime - CHAT_TIME_MARGIN); + + // if it's the first set of replay chunks after seeking, wait until loading has caught up and then apply SEEK_HISTORY_MESSAGE_LIMIT across all the + // messages up to the current time from all the chunks + if (replayChatFirstLoadAfterSeek && !needMoreChat) { + int chatLinesReadyToOutput = 0; + for (const auto & entry : replayChatMessagesPending) { + if (entry.videoOffset > curVideoOffsetMS) { + break; + } + chatLinesReadyToOutput++; + } + qDebug() << "first load after seek; have" << chatLinesReadyToOutput << "current lines"; - if (delay > 1.0 || delay < -1.0) { - qDebug() << "**********************************************************"; - qDebug() << "chat replay delay" << delay << "s -" << message.from << message.message; - qDebug() << "**********************************************************"; + int removeCount = chatLinesReadyToOutput - SEEK_HISTORY_MESSAGE_LIMIT; + if (removeCount > 0) { + qDebug() << "skipping" << removeCount << "lines"; + replayChatMessagesPending.erase(replayChatMessagesPending.begin(), replayChatMessagesPending.begin() + removeCount); } - replayChatMessage(message); - replayChatMessagesPending.pop_front(); + } + + if (!replayChatFirstLoadAfterSeek || !needMoreChat) { + // look at the current time and emit whatever chat messages' timestamps are up + while (!replayChatMessagesPending.empty() && (replayChatMessagesPending.first().videoOffset) <= curVideoOffsetMS) { + auto message = replayChatMessagesPending.first(); + auto delay = (curVideoOffsetMS - (replayChatMessagesPending.first().videoOffset)) / 1000.0; + + if ((delay > 1.0 || delay < -1.0) && !replayChatFirstLoadAfterSeek) { + qDebug() << "**********************************************************"; + qDebug() << "chat replay delay" << delay << "s -" << message.from << message.message; + qDebug() << "**********************************************************"; + } + + replayChatMessage(message); + replayChatMessagesPending.pop_front(); + } + + if (replayChatFirstLoadAfterSeek) { + replayChatFirstLoadAfterSeek = false; + } } - // there is some sketchy logic to figure out what timestamp we actually deem ourselves to have chat up until - double nextChatTime = nextChatChunkTimestamp; - // then if we're close enough to the end of the chat buffer and there isn't a request in progress, we request some more chat - if (!replayChatRequestInProgress && replayChatCurrentTime > (nextChatTime - CHAT_TIME_MARGIN)) { + if (!replayChatRequestInProgress && needMoreChat) { replayChatRequestInProgress = true; _cman->getVodChatPiece(replayVodId, nextChatTime); } - } void IrcChat::handleDownloadedReplayChat(QList<ReplayChatMessage> messages) { @@ -424,17 +476,35 @@ for (auto word = message.begin(); word != message.end(); word++) { bool spacePrefix = word != message.begin(); - QString emoteText = spacePrefix ? word->toString().mid(1) : word->toString(); - auto entry = relevantEmotes.find(emoteText); - if (entry != relevantEmotes.end()) { + QString possibleEmoteText = spacePrefix ? word->toString().mid(1) : word->toString(); + bool isEmote = false; + + auto entry = relevantEmotes.constFind(possibleEmoteText); + if (entry != relevantEmotes.constEnd()) { QString emoteId = entry.value().toString(); _emoteProvider.makeAvailable(emoteId); if (spacePrefix) { output.append(" "); } - output.append(createImageEntry(_emoteProvider.getImageProviderName(), emoteId, emoteText)); + output.append(createImageEntry(_emoteProvider.getImageProviderName(), emoteId, possibleEmoteText)); + isEmote = true; } - else { + + for (const auto & emoteIndex : { lastCurChannelBttvEmoteFixedStrings, lastGlobalBttvEmoteFixedStrings }) { + auto entry = emoteIndex.constFind(possibleEmoteText); + if (entry != emoteIndex.constEnd()) { + QString emoteId = entry.value(); + _bttvEmoteProvider.makeAvailable(emoteId); + if (spacePrefix) { + output.append(" "); + } + output.append(createImageEntry(_bttvEmoteProvider.getImageProviderName(), emoteId, possibleEmoteText)); + isEmote = true; + break; + } + } + + if (!isEmote) { output.append(*word); } } @@ -677,10 +747,6 @@ } } -bool IrcChat::allDownloadsComplete() { - return !_emoteProvider.downloadsInProgress() && _badgeProvider && !_badgeProvider->downloadsInProgress() && _bitsProvider && !_bitsProvider->downloadsInProgress(); -} - void IrcChat::disposeOfMessage(ChatMessage m) { if (allDownloadsComplete()) { emit messageReceived(m.name, m.messageList, m.color, m.subscriber, m.turbo, m.mod, m.isAction, m.badges, m.isChannelNotice, m.systemMessage, m.isWhisper); @@ -836,6 +902,15 @@ } } +void IrcChat::handleBttvEmote(const QString & id, ImagePositionsMap & mapToUpdate, int pos, int end) { + InlineImageInfo info; + info.kind = ImageEntryKind::bttvEmote; + + info.key = _bttvEmoteProvider.getCanonicalKey(id); + mapToUpdate.insert(pos, qMakePair(end, info)); + _bttvEmoteProvider.makeAvailable(id); +} + void updateBitsRegexes(const BitsQStringsMap & bitsUrls, QMap<QString, QRegExp> & mapToUpdate) { mapToUpdate.clear(); @@ -886,7 +961,31 @@ } } + // check for space-separated fixed string tokens int cur = 0; + while (cur < message.length()) { + int wordEnd = message.indexOf(' ', cur); + if (wordEnd == -1) { + wordEnd = message.length(); + } + + if (wordEnd > cur) { + const auto word = message.mid(cur, wordEnd - cur); + + for (const QMap<QString, QString> & bttvIndex : { lastCurChannelBttvEmoteFixedStrings, lastGlobalBttvEmoteFixedStrings }) { + const auto matchEntry = bttvIndex.constFind(word); + if (matchEntry != bttvIndex.constEnd()) { + handleBttvEmote(matchEntry.value(), imagePositionsMap, cur, wordEnd); + break; + } + } + } + + cur = wordEnd + 1; + } + + // go through all text replacement image entries and cut up the input message + cur = 0; for (auto i = imagePositionsMap.constBegin(); i != imagePositionsMap.constEnd(); i++) { auto emoteStart = i.key(); if (emoteStart > cur) { @@ -900,25 +999,31 @@ QString originalText = message.mid(emoteStart, emoteAfterEnd - emoteStart); QVariantMap imgEntry; + bool doInsert = true; switch (imageKind) { case ImageEntryKind::emote: imgEntry = createImageEntry(_emoteProvider.getImageProviderName(), imageId, originalText); - imgEntry.insert("textSuffix", imageInfo.textSuffix); - imgEntry.insert("textSuffixColor", imageInfo.textSuffixColor); - messageList.append(imgEntry); + break; + case ImageEntryKind::bttvEmote: + imgEntry = createImageEntry(_bttvEmoteProvider.getImageProviderName(), imageId, originalText); break; case ImageEntryKind::bits: if (_bitsProvider) { imgEntry = createImageEntry(_bitsProvider->getImageProviderName(), imageId, originalText); // currently QML AnimatedImage doesn't support using images from a QQuickImageProvider imgEntry.insert("sourceUrl", _cman->getBitsUrlForKey(imageId).toString()); - imgEntry.insert("textSuffix", imageInfo.textSuffix); - imgEntry.insert("textSuffixColor", imageInfo.textSuffixColor); - messageList.append(imgEntry); + } + else { + doInsert = false; } break; } + if (doInsert) { + imgEntry.insert("textSuffix", imageInfo.textSuffix); + imgEntry.insert("textSuffixColor", imageInfo.textSuffixColor); + messageList.append(imgEntry); + } cur = emoteAfterEnd; } if (cur < message.length()) { @@ -1232,6 +1337,22 @@ _emoteProvider.bulkDownload(keys); } +QList<QString> valuesList(const QMap<QString, QString> & map) { + QList<QString> out; + for (auto entry = map.constBegin(); entry != map.constEnd(); entry++) { + out.append(entry.value()); + } + return out; +} + +void IrcChat::downloadBttvEmotesGlobal() { + _bttvEmoteProvider.bulkDownload(valuesList(lastGlobalBttvEmoteFixedStrings)); +} + +void IrcChat::downloadBttvEmotesChannel() { + _bttvEmoteProvider.bulkDownload(valuesList(lastCurChannelBttvEmoteFixedStrings)); +} + void IrcChat::blockedUsersLoaded(const QSet<QString> & newBlockedUsers) { blockedUsers = newBlockedUsers; } @@ -1255,3 +1376,25 @@ blockedUsers.remove(newUnblockedUsername); } } + +template <typename U> +QVariantMap toVariantMap(const QMap<QString, U> & map) { + QVariantMap out; + for (auto entry = map.constBegin(); entry != map.constEnd(); entry++) { + out.insert(entry.key(), entry.value()); + } + return out; +} + +void IrcChat::handleChannelBttvEmotesLoaded(const QString & channelName, QMap<QString, QString> emotesByCode) { + const QString GLOBAL_EMOTES_ID = "GLOBAL"; + if (channelName == GLOBAL_EMOTES_ID) { + lastGlobalBttvEmoteFixedStrings = emotesByCode; + } + else if (channelName == room) { + lastCurChannelBttvEmoteFixedStrings = emotesByCode; + } + + emit bttvEmotesLoaded(channelName, toVariantMap(emotesByCode)); +} + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/model/ircchat.h new/orion-1.5.1+git~20170602/src/model/ircchat.h --- old/orion-1.5.1+git~20170519/src/model/ircchat.h 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/model/ircchat.h 2017-06-02 07:42:07.000000000 +0200 @@ -119,6 +119,8 @@ bool downloadError(); void bulkDownloadComplete(); + + void bttvEmotesLoaded(QString channel, QVariantMap emotesByCode); public slots: void sendMessage(const QString &msg, const QVariantMap &relevantEmotes); @@ -126,7 +128,8 @@ void login(); void bulkDownloadEmotes(QList<QString> keys); - + void downloadBttvEmotesGlobal(); + void downloadBttvEmotesChannel(); private slots: void receive(); void processError(QAbstractSocket::SocketError socketError); @@ -139,6 +142,8 @@ void userBlocked(const QString & blockedUsername); void userUnblocked(const QString & unblockedUsername); + void handleChannelBttvEmotesLoaded(const QString & channelName, QMap<QString, QString> emotesByCode); + private: static bool hiDpi; @@ -148,9 +153,13 @@ static const QString IMAGE_PROVIDER_EMOTE; static const QString EMOTICONS_URL_FORMAT_HIDPI; static const QString EMOTICONS_URL_FORMAT_LODPI; + static const QString IMAGE_PROVIDER_BTTV_EMOTE; + static const QString BTTV_EMOTES_URL_FORMAT_HIDPI; + static const QString BTTV_EMOTES_URL_FORMAT_LODPI; static const QString IMAGE_PROVIDER_BITS; URLFormatImageProvider _emoteProvider; + URLFormatImageProvider _bttvEmoteProvider; BitsImageProvider * _bitsProvider; BadgeImageProvider * _badgeProvider; ChannelManager * _cman; @@ -196,6 +205,7 @@ bool allDownloadsComplete(); bool replayChatRequestInProgress; + bool replayChatFirstLoadAfterSeek; double replayChatVodStartTime; double replayChatFirstChunkTime; @@ -212,7 +222,10 @@ QMap<QString, QRegExp> lastCurChannelBitsRegexes; QMap<QString, QRegExp> lastGlobalBitsRegexes; - enum ImageEntryKind { emote, bits }; + QMap<QString, QString> lastGlobalBttvEmoteFixedStrings; + QMap<QString, QString> lastCurChannelBttvEmoteFixedStrings; + + enum ImageEntryKind { emote, bits, bttvEmote }; struct InlineImageInfo { ImageEntryKind kind; @@ -225,6 +238,8 @@ void checkBitsRegex(const QRegExp & regex, const QString & prefix, const QString & message, ImagePositionsMap & mapToUpdate); + void handleBttvEmote(const QString & id, ImagePositionsMap & mapToUpdate, int pos, int end); + void roomInitCommon(const QString channel, const QString channelId); void setUserBlock(const QString & username, const bool blocked); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/network/networkmanager.cpp new/orion-1.5.1+git~20170602/src/network/networkmanager.cpp --- old/orion-1.5.1+git~20170519/src/network/networkmanager.cpp 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/network/networkmanager.cpp 2017-06-02 07:42:07.000000000 +0200 @@ -553,11 +553,11 @@ QByteArray data = reply->readAll(); - QList<QString> ret = JsonParser::parseBlockList(data); + auto result = JsonParser::parseBlockList(data); int nextOffset = reply->request().attribute(QNetworkRequest::User).toInt(); - emit blockedUserListLoadOperationFinished(ret, nextOffset); + emit blockedUserListLoadOperationFinished(result.items, nextOffset, result.total); reply->deleteLater(); } @@ -831,6 +831,66 @@ reply->deleteLater(); } +void NetworkManager::getChannelBttvEmotes(const QString channel) { + QString url = QString(BTTV_API) + QString("/channels/") + QUrl::toPercentEncoding(channel); + + qDebug() << "Requesting" << url; + + QNetworkRequest request; + request.setUrl(QUrl(url)); + + QNetworkReply *reply = operation->get(request); + + connect(reply, &QNetworkReply::finished, this, &NetworkManager::channelBttvEmotesReply); +} + +void NetworkManager::channelBttvEmotesReply() { + QNetworkReply* reply = qobject_cast<QNetworkReply *>(sender()); + + if (!handleNetworkError(reply)) { + return; + } + QByteArray data = reply->readAll(); + + auto url = reply->url(); + QString urlString = url.toString(); + QString channel = urlString.mid(urlString.lastIndexOf("/") + 1); + + auto emotes = JsonParser::parseBttvEmotesData(data); + + emit getChannelBttvEmotesOperationFinished(channel, emotes); + + reply->deleteLater(); +} + +void NetworkManager::getGlobalBttvEmotes() { + QString url = QString(BTTV_API) + QString("/emotes"); + + qDebug() << "Requesting" << url; + + QNetworkRequest request; + request.setUrl(QUrl(url)); + + QNetworkReply *reply = operation->get(request); + + connect(reply, &QNetworkReply::finished, this, &NetworkManager::globalBttvEmotesReply); +} + +void NetworkManager::globalBttvEmotesReply() { + QNetworkReply* reply = qobject_cast<QNetworkReply *>(sender()); + + if (!handleNetworkError(reply)) { + return; + } + QByteArray data = reply->readAll(); + + auto emotes = JsonParser::parseBttvEmotesData(data); + + emit getGlobalBttvEmotesOperationFinished(emotes); + + reply->deleteLater(); +} + void NetworkManager::editUserFavourite(const QString &access_token, const quint64 userId, const quint64 channelId, bool add) { QString url = QString(KRAKEN_API) + "/users/" + QString::number(userId) @@ -974,11 +1034,11 @@ addULongLongStringList(queriedChannelIds, query.queryItemValue("channel").split(",")); } - QList<Channel *> out = JsonParser::parseStreams(data); + PagedResult<Channel *> out = JsonParser::parseStreams(data); - addOfflineChannels(out, queriedChannelIds); + addOfflineChannels(out.items, queriedChannelIds); - emit allStreamsOperationFinished(out); + emit allStreamsOperationFinished(out.items); reply->deleteLater(); } @@ -1030,11 +1090,11 @@ addULongLongStringList(queriedChannelIds, query.queryItemValue("channel").split(",")); } - QList<Channel *> out = JsonParser::parseStreams(data); + PagedResult<Channel *> out = JsonParser::parseStreams(data); - addOfflineChannels(out, queriedChannelIds); + addOfflineChannels(out.items, queriedChannelIds); - emit gameStreamsOperationFinished(out); + emit gameStreamsOperationFinished(out.items, out.total); reply->deleteLater(); } @@ -1066,7 +1126,8 @@ //qDebug() << data; - emit searchChannelsOperationFinished(JsonParser::parseChannels(data)); + auto result = JsonParser::parseChannels(data); + emit searchChannelsOperationFinished(result.items, result.total); reply->deleteLater(); } @@ -1156,7 +1217,8 @@ int offset = reply->request().attribute(QNetworkRequest::User).toInt(); - emit favouritesReplyFinished(JsonParser::parseFavourites(data), offset); + auto result = JsonParser::parseFavourites(data); + emit favouritesReplyFinished(result.items, offset, result.total); reply->deleteLater(); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/network/networkmanager.h new/orion-1.5.1+git~20170602/src/network/networkmanager.h --- old/orion-1.5.1+git~20170519/src/network/networkmanager.h 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/network/networkmanager.h 2017-06-02 07:42:07.000000000 +0200 @@ -81,6 +81,8 @@ void getGlobalBadgesUrlsBeta(); void getChannelBitsUrls(const int channelID); void getGlobalBitsUrls(); + void getChannelBttvEmotes(const QString channel); + void getGlobalBttvEmotes(); Q_INVOKABLE void getVodStartTime(quint64 vodId); Q_INVOKABLE void getVodChatPiece(quint64 vodId, quint64 offset); @@ -101,15 +103,15 @@ void allStreamsOperationFinished(const QList<Channel *>&); void gamesOperationFinished(const QList<Game *>&); - void gameStreamsOperationFinished(const QList<Channel *>&); + void gameStreamsOperationFinished(const QList<Channel *>&, const int total); void featuredStreamsOperationFinished(const QList<Channel *>&); - void searchChannelsOperationFinished(const QList<Channel *>&); + void searchChannelsOperationFinished(const QList<Channel *>&, const int total); void searchGamesOperationFinished(const QList<Game *>&); void broadcastsOperationFinished(const QList<Vod *>&); void m3u8OperationFinished(const QVariantMap&); void m3u8OperationBFinished(const QVariantMap&); void fileOperationFinished(const QByteArray&); - void favouritesReplyFinished(const QList<Channel *>&, const quint32); + void favouritesReplyFinished(const QList<Channel *>&, const quint32, const quint32); void streamGetOperationFinished(const quint64 channelId, const bool online); void error(const QString &error); @@ -124,11 +126,14 @@ void vodStartGetOperationFinished(double); void vodChatPieceGetOperationFinished(QList<ReplayChatMessage>); void chatterListLoadOperationFinished(QMap<QString, QList<QString>>); - void blockedUserListLoadOperationFinished(QList<QString>, const quint32 nextOffset); + void blockedUserListLoadOperationFinished(QList<QString>, const quint32 nextOffset, const quint32 total); void getChannelBitsUrlsOperationFinished(int channelID, BitsQStringsMap channelBitsUrls, BitsQStringsMap channelBitsColors); void getGlobalBitsUrlsOperationFinished(BitsQStringsMap globalBitsUrls, BitsQStringsMap globalBitsColors); + void getChannelBttvEmotesOperationFinished(const QString channel, QMap<QString, QString> & emotesByCode); + void getGlobalBttvEmotesOperationFinished(QMap<QString, QString> & emotesByCode); + void networkAccessChanged(bool up); void userBlocked(quint64 myUserId, const QString & blockedUsername); @@ -166,6 +171,8 @@ void channelBitsUrlsReply(); void globalBitsUrlsReply(); void blockUserLookupReply(); + void globalBttvEmotesReply(); + void channelBttvEmotesReply(); private: static const QString CHANNEL_BADGES_URL_PREFIX; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/network/urls.h new/orion-1.5.1+git~20170602/src/network/urls.h --- old/orion-1.5.1+git~20170519/src/network/urls.h 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/network/urls.h 2017-06-02 07:42:07.000000000 +0200 @@ -20,6 +20,7 @@ #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 CLIENT_ID "0dpzlnp1w2bjlim3ldp0u96o4dq2gm" #endif // URLS_H diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/qml/SearchView.qml new/orion-1.5.1+git~20170602/src/qml/SearchView.qml --- old/orion-1.5.1+git~20170519/src/qml/SearchView.qml 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/qml/SearchView.qml 2017-06-02 07:42:07.000000000 +0200 @@ -65,7 +65,7 @@ _spinner.visible = false _button.visible = true - channels.checkScrolled() + channels.checkScrolled(total) } onSearchingStarted: { @@ -191,7 +191,10 @@ model: g_results - function checkScrolled(){ + function checkScrolled(total){ + if (total != null && itemCount >= total) { + return; + } if (atYEnd && model.count() === itemCount && itemCount > 0){ search(_input.text, itemCount, 25, false); itemCount += 25 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/qml/irc/Chat.qml new/orion-1.5.1+git~20170602/src/qml/irc/Chat.qml --- old/orion-1.5.1+git~20170519/src/qml/irc/Chat.qml 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/qml/irc/Chat.qml 2017-06-02 07:42:07.000000000 +0200 @@ -28,6 +28,7 @@ signal bulkDownloadComplete() signal channelBadgeUrlsLoaded(int channelId, var badgeUrls) signal channelBadgeBetaUrlsLoaded(string channel, var badgeSetData) + signal bttvEmotesLoaded(string channel, var emotesByCode) property alias isAnonymous: chat.anonymous property var channel: undefined @@ -72,6 +73,7 @@ g_cman.loadChannelBadgeUrls(channelId); g_cman.loadChannelBetaBadgeUrls(channelId); g_cman.loadChannelBitsUrls(channelId); + g_cman.loadChannelBttvEmotes(channelName); } function joinChannel(channelName, channelId) { @@ -123,6 +125,14 @@ return chat.bulkDownloadEmotes(emotes); } + function downloadBttvEmotesGlobal() { + return chat.downloadBttvEmotesGlobal(); + } + + function downloadBttvEmotesChannel() { + return chat.downloadBttvEmotesChannel(); + } + function reconnect() { leaveChannel() if (root.channel) @@ -168,5 +178,9 @@ onBulkDownloadComplete: { root.bulkDownloadComplete(); } + + onBttvEmotesLoaded: { + root.bttvEmotesLoaded(channel, emotesByCode); + } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/qml/irc/ChatView.qml new/orion-1.5.1+git~20170602/src/qml/irc/ChatView.qml --- old/orion-1.5.1+git~20170519/src/qml/irc/ChatView.qml 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/qml/irc/ChatView.qml 2017-06-02 07:42:07.000000000 +0200 @@ -58,22 +58,30 @@ } } + function cleanupPrevChannel() { + if (chat.lastBttvChannelEmotes != null) { + _emoteButton.clearChannelSpecificEmotes() + chat.lastBttvChannelEmotes = null; + } + chatModel.clear() + } + function joinChannel(channel, channelId) { if (channel !== chat.channel || chat.replayMode) { viewerListEnabled = false; - chatModel.clear() + cleanupPrevChannel() chat.joinChannel(channel, channelId) } } function leaveChannel() { - chatModel.clear() + cleanupPrevChannel() chat.leaveChannel() } function replayChat(channelName, channelId, vodId, startEpochTime) { viewerListEnabled = false; - chatModel.clear() + cleanupPrevChannel() chat.leaveChannel() chat.replayChat(channelName, channelId, vodId, startEpochTime); } @@ -118,12 +126,20 @@ function loadEmotes() { //console.log("loadEmotes()", chat.lastEmoteSets); - if (chat.lastEmoteSets) { + if (!_emoteButton.pickerLoaded) { + if (chat.lastEmoteSets) { + _emotePicker.loading = true; + //console.log("downloading chat.lastEmoteSets", chat.lastEmoteSets); + _emoteButton.startDownload(chat.lastEmoteSets); + } + } else if (_emoteButton.pickerChannelLoaded != chat.channel) { + // just download channel emotes we don't have yet _emotePicker.loading = true; - //console.log("downloading chat.lastEmoteSets", chat.lastEmoteSets); - _emoteButton.startDownload(chat.lastEmoteSets); + _emoteButton.startDownload(null); } + _emoteButton.pickerLoaded = true; + _emoteButton.pickerChannelLoaded = chat.channel; } Connections { @@ -548,6 +564,7 @@ property ListModel setsVisible: ListModel { } property bool pickerLoaded: false + property var pickerChannelLoaded: null visible: root.chatViewVisible @@ -564,7 +581,7 @@ Connections { target: _emotePicker onVisibleChanged: { - if (_emotePicker.visible && !_emoteButton.pickerLoaded) { + if (_emotePicker.visible) { loadEmotes(); } } @@ -744,27 +761,65 @@ function showLastSet() { //console.log("showing last set", lastSet); - var lastSetMap = lastEmoteSets[lastSet]; - for (var i in lastSetMap) { - setsVisible.append({"imageUrl": "image://emote/" + i, "emoteName": decodeHtml(inverseRegex(lastSetMap[i]))}) + switch(lastSet) { + case "bttvGlobal": + for (var i in chat.lastBttvGlobalEmotes) { + setsVisible.append({"imageUrl": "image://bttvemote/" + chat.lastBttvGlobalEmotes[i], "emoteName": i}); + } + break; + case "bttvChannel": + for (var i in chat.lastBttvChannelEmotes) { + setsVisible.append({"imageUrl": "image://bttvemote/" + chat.lastBttvChannelEmotes[i], "emoteName": i}); + } + break; + default: + var lastSetMap = lastEmoteSets[lastSet]; + for (var i in lastSetMap) { + setsVisible.append({"imageUrl": "image://emote/" + i, "emoteName": decodeHtml(inverseRegex(lastSetMap[i]))}) + } + break; } _emotePicker.updateFilter(); } + function clearChannelSpecificEmotes() { + //console.log("clearChannelSpecificEmotes()") + var channelEmotes = chat.lastBttvChannelEmotes; + if (channelEmotes != null) { + for (var i = 0; i < setsVisible.count; ) { + var obj = setsVisible.get(i); + if (channelEmotes.hasOwnProperty(obj.emoteName)) { + //console.log("remove channel emote", obj.emoteName, i); + setsVisible.remove(i); + } else { + i++; + } + } + } + _emoteButton.pickerChannelLoaded = null; + } + function nextDownload() { if (emotePickerDownloadsInProgress) { if (curDownloading < setsToDownload.length) { var curSetID = setsToDownload[curDownloading]; lastSet = curSetID; - var curSetMap = lastEmoteSets[curSetID]; - var curSetList = []; - for (var i in curSetMap) { - curSetList.push(i); - } curDownloading ++; console.log("Downloading emote set #", curDownloading, curSetID); - chat.bulkDownloadEmotes(curSetList); + if (curSetID == "bttvGlobal") { + chat.downloadBttvEmotesGlobal(); + } else if (curSetID == "bttvChannel") { + chat.downloadBttvEmotesChannel(); + } else { + var curSetMap = lastEmoteSets[curSetID]; + var curSetList = []; + for (var i in curSetMap) { + curSetList.push(i); + } + chat.bulkDownloadEmotes(curSetList); + } } else { + console.log("Emote set downloads complete"); emotePickerDownloadsInProgress = false; _emotePicker.loading = false; } @@ -773,10 +828,16 @@ function startDownload(emoteSets) { curDownloading = 0; - lastEmoteSets = emoteSets; setsToDownload = []; - for (var i in emoteSets) { - setsToDownload.push(i); + if (emoteSets != null) { + lastEmoteSets = emoteSets; + for (var i in emoteSets) { + setsToDownload.push(i); + } + setsToDownload.push("bttvGlobal"); + } + if (chat.lastBttvChannelEmotes != null) { + setsToDownload.push("bttvChannel"); } //console.log("Starting download of emote sets", setsToDownload); emotePickerDownloadsInProgress = true; @@ -808,6 +869,8 @@ property string emoteDirPath property variant lastEmoteSetIDs property variant lastEmoteSets + property variant lastBttvChannelEmotes + property variant lastBttvGlobalEmotes property variant _textEmotesMap property variant _regexEmotesList @@ -823,6 +886,23 @@ initEmotesMaps(); } + onBttvEmotesLoaded: { + /* + console.log("received bttv emotes for", channel); + for (var i in emotesByCode) { + console.log("code", i, "id", emotesByCode[i]); + } + */ + + if (channel == "GLOBAL") { + chat.lastBttvGlobalEmotes = emotesByCode; + } else if (channel == chat.channel) { + chat.lastBttvChannelEmotes = emotesByCode; + } else { + console.log("bttv emotes loaded for a different channel", channel); + } + } + function regexExactMatch(regex, text) { var match = regex.exec(text); return match && match[0] === text; @@ -1031,7 +1111,7 @@ } onClear: { - chatModel.clear() + cleanupPrevChannel() } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/util/jsonparser.cpp new/orion-1.5.1+git~20170602/src/util/jsonparser.cpp --- old/orion-1.5.1+git~20170519/src/util/jsonparser.cpp 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/util/jsonparser.cpp 2017-06-02 07:42:07.000000000 +0200 @@ -20,9 +20,9 @@ hiDpi = setting; } -QList<Channel*> JsonParser::parseStreams(const QByteArray &data) +PagedResult<Channel*> JsonParser::parseStreams(const QByteArray &data) { - QList<Channel*> channels; + PagedResult<Channel*> out; QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(data,&error); @@ -32,13 +32,15 @@ //Online streams QJsonArray arr = json["streams"].toArray(); foreach (const QJsonValue &item, arr){ - channels.append(JsonParser::parseStreamJson(item.toObject(), true)); + out.items.append(JsonParser::parseStreamJson(item.toObject(), true)); } + out.total = json["_total"].toInt(); + //Caller must use request context to determine offline streams } - return channels; + return out; } Channel *JsonParser::parseStream(const QByteArray &data) @@ -252,9 +254,9 @@ return vod; } -QList<Channel*> JsonParser::parseChannels(const QByteArray &data) +PagedResult<Channel*> JsonParser::parseChannels(const QByteArray &data) { - QList<Channel*> channels; + PagedResult<Channel*> out; QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(data,&error); @@ -263,29 +265,33 @@ QJsonArray arr = json["channels"].toArray(); foreach (const QJsonValue &item, arr){ - channels.append(JsonParser::parseChannelJson(item.toObject())); + out.items.append(JsonParser::parseChannelJson(item.toObject())); } + + out.total = json["_total"].toInt(); } - return channels; + return out; } -QList<Channel *> JsonParser::parseFavourites(const QByteArray &data) +PagedResult<Channel *> JsonParser::parseFavourites(const QByteArray &data) { - QList<Channel*> channels; + PagedResult<Channel *> out; QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(data,&error); if (error.error == QJsonParseError::NoError){ QJsonObject json = doc.object(); + out.total = json["_total"].toInt(); + QJsonArray arr = json["follows"].toArray(); foreach (const QJsonValue &item, arr){ - channels.append(JsonParser::parseChannelJson(item.toObject()["channel"].toObject())); + out.items.append(JsonParser::parseChannelJson(item.toObject()["channel"].toObject())); } } - return channels; + return out; } QList<Channel *> JsonParser::parseFeatured(const QByteArray &data) @@ -705,9 +711,9 @@ } -QList<QString> JsonParser::parseBlockList(const QByteArray &data) +PagedResult<QString> JsonParser::parseBlockList(const QByteArray &data) { - QList<QString> out; + PagedResult<QString> out; QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(data, &error); @@ -715,16 +721,44 @@ if (error.error == QJsonParseError::NoError) { QJsonObject json = doc.object(); + out.total = json["_total"].toInt(); + QJsonArray blocks = json["blocks"].toArray(); for (const auto & block : blocks) { const auto & blockObj = block.toObject(); const auto & name = blockObj["user"].toObject()["name"].toString(); if (!name.isEmpty()) { - out.append(name); + out.items.append(name); } } } return out; } + + +QMap<QString, QString> JsonParser::parseBttvEmotesData(const QByteArray &data) +{ + QMap<QString, QString> out; + + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + + if (error.error == QJsonParseError::NoError) { + QJsonObject json = doc.object(); + + QJsonArray emotes = json["emotes"].toArray(); + + for (const auto & emote : emotes) { + const auto & emoteObj = emote.toObject(); + const auto & id = emoteObj["id"].toString(); + const auto & code = emoteObj["code"].toString(); + if (!id.isEmpty() && !code.isEmpty()) { + out.insert(code, id); + } + } + } + + return out; +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/orion-1.5.1+git~20170519/src/util/jsonparser.h new/orion-1.5.1+git~20170602/src/util/jsonparser.h --- old/orion-1.5.1+git~20170519/src/util/jsonparser.h 2017-05-19 14:24:40.000000000 +0200 +++ new/orion-1.5.1+git~20170602/src/util/jsonparser.h 2017-06-02 07:42:07.000000000 +0200 @@ -31,13 +31,19 @@ * Handles parsing of json documents to business logic-objects */ +template <typename U> +struct PagedResult { + QList<U> items; + int total; +}; + class JsonParser { public: - static QList<Channel*> parseStreams(const QByteArray&); + static PagedResult<Channel*> parseStreams(const QByteArray&); static QList<Game*> parseGames(const QByteArray&); - static QList<Channel*> parseChannels(const QByteArray&); - static QList<Channel*> parseFavourites(const QByteArray&); + static PagedResult<Channel*> parseChannels(const QByteArray&); + static PagedResult<Channel*> parseFavourites(const QByteArray&); static QList<Channel*> parseFeatured(const QByteArray&); static QList<Vod *> parseVods(const QByteArray&); static Game* parseGame(const QJsonObject&); @@ -56,8 +62,9 @@ static QMap<QString, QMap<QString, QMap<QString, QString>>> parseBadgeUrlsBetaFormat(const QByteArray &data); static QList<ReplayChatMessage> parseVodChatPiece(const QByteArray &data); static QMap<QString, QList<QString>> parseChatterList(const QByteArray &data); - static QList<QString> parseBlockList(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); + static QMap<QString, QString> parseBttvEmotesData(const QByteArray &data); static void setHiDpi(bool setting); private: static bool hiDpi;
