fixed image animations

This commit is contained in:
fourtf 2018-08-11 17:15:17 +02:00
parent c719bb6b74
commit c768bd9bd9
16 changed files with 229 additions and 210 deletions

View file

@ -137,7 +137,7 @@ void CompletionModel::refresh()
getApp() getApp()
->twitch2->getChannelOrEmptyByID(this->channelName_) ->twitch2->getChannelOrEmptyByID(this->channelName_)
.get())) { .get())) {
auto bttv = channel->accessBttvEmotes(); auto bttv = channel->bttvEmotes();
// auto it = bttv->begin(); // auto it = bttv->begin();
// for (const auto &emote : *bttv) { // for (const auto &emote : *bttv) {
// } // }
@ -148,7 +148,7 @@ void CompletionModel::refresh()
// } // }
// Channel-specific: FFZ Channel Emotes // Channel-specific: FFZ Channel Emotes
for (const auto &emote : *channel->accessFfzEmotes()) { for (const auto &emote : *channel->ffzEmotes()) {
this->addString(emote.second->name.string, this->addString(emote.second->name.string,
TaggedString::Type::FFZChannelEmote); TaggedString::Type::FFZChannelEmote);
} }

View file

@ -24,4 +24,22 @@ EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache)
return std::make_shared<Emote>(std::move(emote)); return std::make_shared<Emote>(std::move(emote));
} }
EmotePtr cachedOrMakeEmotePtr(
Emote &&emote,
std::unordered_map<EmoteId, std::weak_ptr<const Emote>> &cache,
std::mutex &mutex, const EmoteId &id)
{
std::lock_guard<std::mutex> guard(mutex);
auto shared = cache[id].lock();
if (shared && *shared == emote) {
// reuse old shared_ptr if nothing changed
return shared;
} else {
shared = std::make_shared<Emote>(std::move(emote));
cache[id] = shared;
return shared;
}
}
} // namespace chatterino } // namespace chatterino

View file

@ -38,5 +38,9 @@ using WeakEmoteMap = std::unordered_map<EmoteName, std::weak_ptr<const Emote>>;
using WeakEmoteIdMap = std::unordered_map<EmoteId, std::weak_ptr<const Emote>>; using WeakEmoteIdMap = std::unordered_map<EmoteId, std::weak_ptr<const Emote>>;
EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache); EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache);
EmotePtr cachedOrMakeEmotePtr(
Emote &&emote,
std::unordered_map<EmoteId, std::weak_ptr<const Emote>> &cache,
std::mutex &mutex, const EmoteId &id);
} // namespace chatterino } // namespace chatterino

View file

@ -30,14 +30,27 @@ Frames::Frames()
Frames::Frames(const QVector<Frame<QPixmap>> &frames) Frames::Frames(const QVector<Frame<QPixmap>> &frames)
: items_(frames) : items_(frames)
{ {
assertInGuiThread();
DebugCount::increase("images"); DebugCount::increase("images");
if (this->animated()) DebugCount::increase("animated images");
if (this->animated()) {
DebugCount::increase("animated images");
this->gifTimerConnection_ = getApp()->emotes->gifTimer.signal.connect(
[this] { this->advance(); });
}
} }
Frames::~Frames() Frames::~Frames()
{ {
assertInGuiThread();
DebugCount::decrease("images"); DebugCount::decrease("images");
if (this->animated()) DebugCount::decrease("animated images");
if (this->animated()) {
DebugCount::decrease("animated images");
}
this->gifTimerConnection_.disconnect();
} }
void Frames::advance() void Frames::advance()
@ -46,6 +59,11 @@ void Frames::advance()
while (true) { while (true) {
this->index_ %= this->items_.size(); this->index_ %= this->items_.size();
if (this->index_ >= this->items_.size()) {
this->index_ = this->index_;
}
if (this->durationOffset_ > this->items_[this->index_].duration) { if (this->durationOffset_ > this->items_[this->index_].duration) {
this->durationOffset_ -= this->items_[this->index_].duration; this->durationOffset_ -= this->items_[this->index_].duration;
this->index_ = (this->index_ + 1) % this->items_.size(); this->index_ = (this->index_ + 1) % this->items_.size();
@ -195,12 +213,14 @@ Image::Image(const Url &url, qreal scale)
: url_(url) : url_(url)
, scale_(scale) , scale_(scale)
, shouldLoad_(true) , shouldLoad_(true)
, frames_(std::make_unique<Frames>())
{ {
} }
Image::Image(const QPixmap &pixmap, qreal scale) Image::Image(const QPixmap &pixmap, qreal scale)
: scale_(scale) : scale_(scale)
, frames_(QVector<Frame<QPixmap>>{Frame<QPixmap>{pixmap, 1}}) , frames_(std::make_unique<Frames>(
QVector<Frame<QPixmap>>{Frame<QPixmap>{pixmap, 1}}))
{ {
} }
@ -218,7 +238,7 @@ boost::optional<QPixmap> Image::pixmap() const
const_cast<Image *>(this)->load(); const_cast<Image *>(this)->load();
} }
return this->frames_.current(); return this->frames_->current();
} }
qreal Image::scale() const qreal Image::scale() const
@ -235,14 +255,14 @@ bool Image::animated() const
{ {
assertInGuiThread(); assertInGuiThread();
return this->frames_.animated(); return this->frames_->animated();
} }
int Image::width() const int Image::width() const
{ {
assertInGuiThread(); assertInGuiThread();
if (auto pixmap = this->frames_.first()) if (auto pixmap = this->frames_->first())
return pixmap->width() * this->scale_; return pixmap->width() * this->scale_;
else else
return 16; return 16;
@ -252,7 +272,7 @@ int Image::height() const
{ {
assertInGuiThread(); assertInGuiThread();
if (auto pixmap = this->frames_.first()) if (auto pixmap = this->frames_->first())
return pixmap->height() * this->scale_; return pixmap->height() * this->scale_;
else else
return 16; return 16;
@ -277,7 +297,8 @@ void Image::load()
auto parsed = readFrames(reader, that->url()); auto parsed = readFrames(reader, that->url());
postToThread(makeConvertCallback(parsed, [weak](auto frames) { postToThread(makeConvertCallback(parsed, [weak](auto frames) {
if (auto shared = weak.lock()) shared->frames_ = frames; if (auto shared = weak.lock())
shared->frames_ = std::make_unique<Frames>(frames);
})); }));
return Success; return Success;
@ -290,7 +311,7 @@ bool Image::operator==(const Image &other) const
{ {
if (this->isEmpty() && other.isEmpty()) return true; if (this->isEmpty() && other.isEmpty()) return true;
if (!this->url_.string.isEmpty() && this->url_ == other.url_) return true; if (!this->url_.string.isEmpty() && this->url_ == other.url_) return true;
if (this->frames_.first() == other.frames_.first()) return true; if (this->frames_->first() == other.frames_->first()) return true;
return false; return false;
} }

View file

@ -11,6 +11,7 @@
#include <boost/variant.hpp> #include <boost/variant.hpp>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <pajlada/signals/signal.hpp>
#include "common/NullablePtr.hpp" #include "common/NullablePtr.hpp"
@ -21,14 +22,12 @@ struct Frame {
Image image; Image image;
int duration; int duration;
}; };
class Frames class Frames : boost::noncopyable
{ {
public: public:
Frames(); Frames();
Frames(const QVector<Frame<QPixmap>> &frames); Frames(const QVector<Frame<QPixmap>> &frames);
~Frames(); ~Frames();
Frames(Frames &&other) = default;
Frames &operator=(Frames &&other) = default;
bool animated() const; bool animated() const;
void advance(); void advance();
@ -39,6 +38,7 @@ private:
QVector<Frame<QPixmap>> items_; QVector<Frame<QPixmap>> items_;
int index_{0}; int index_{0};
int durationOffset_{0}; int durationOffset_{0};
pajlada::Signals::Connection gifTimerConnection_;
}; };
} // namespace } // namespace
@ -74,7 +74,7 @@ private:
qreal scale_{1}; qreal scale_{1};
bool empty_{false}; bool empty_{false};
bool shouldLoad_{false}; bool shouldLoad_{false};
Frames frames_{}; std::unique_ptr<Frames> frames_{};
QObject object_{}; QObject object_{};
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -44,8 +44,45 @@ std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonObject &jsonRoot,
return {Success, std::move(emotes)}; return {Success, std::move(emotes)};
} }
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
{
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
static std::mutex mutex;
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
}
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
{
auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate = "https:" + jsonRoot.value("urlTemplate").toString();
for (auto jsonEmote_ : jsonEmotes) {
auto jsonEmote = jsonEmote_.toObject();
auto id = EmoteId{jsonEmote.value("id").toString()};
auto name = EmoteName{jsonEmote.value("code").toString()};
// emoteObject.value("imageType").toString();
auto emote = Emote(
{name,
ImageSet{
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
Tooltip{name.string + "<br />Channel Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
emotes[name] = cachedOrMake(std::move(emote), id);
}
return {Success, std::move(emotes)};
}
} // namespace } // namespace
//
// BttvEmotes
//
BttvEmotes::BttvEmotes() BttvEmotes::BttvEmotes()
: global_(std::make_shared<EmoteMap>()) : global_(std::make_shared<EmoteMap>())
{ {
@ -84,4 +121,31 @@ void BttvEmotes::loadGlobal()
request.execute(); request.execute();
} }
void BttvEmotes::loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{
auto request =
NetworkRequest(QString(bttvChannelEmoteApiUrl) + channelName);
request.setCaller(QThread::currentThread());
request.setTimeout(3000);
request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
auto pair = parseChannelEmotes(result.parseJson());
if (pair.first) callback(std::move(pair.second));
return pair.first;
});
request.execute();
}
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{
urlTemplate.detach();
return {urlTemplate.replace("{{id}}", id.string)
.replace("{{image}}", emoteScale)};
}
} // namespace chatterino } // namespace chatterino

View file

@ -10,6 +10,8 @@ class BttvEmotes final
{ {
static constexpr const char *globalEmoteApiUrl = static constexpr const char *globalEmoteApiUrl =
"https://api.betterttv.net/2/emotes"; "https://api.betterttv.net/2/emotes";
static constexpr const char *bttvChannelEmoteApiUrl =
"https://api.betterttv.net/2/channels/";
public: public:
BttvEmotes(); BttvEmotes();
@ -17,6 +19,8 @@ public:
std::shared_ptr<const EmoteMap> global() const; std::shared_ptr<const EmoteMap> global() const;
boost::optional<EmotePtr> global(const EmoteName &name) const; boost::optional<EmotePtr> global(const EmoteName &name) const;
void loadGlobal(); void loadGlobal();
static void loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback);
private: private:
Atomic<std::shared_ptr<const EmoteMap>> global_; Atomic<std::shared_ptr<const EmoteMap>> global_;

View file

@ -11,78 +11,4 @@
namespace chatterino { namespace chatterino {
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale);
static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(
const QJsonObject &jsonRoot);
void loadBttvChannelEmotes(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{
auto request =
NetworkRequest(QString(bttvChannelEmoteApiUrl) + channelName);
request.setCaller(QThread::currentThread());
request.setTimeout(3000);
request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
auto pair = bttvParseChannelEmotes(result.parseJson());
if (pair.first == Success) callback(std::move(pair.second));
return pair.first;
});
request.execute();
}
static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(
const QJsonObject &jsonRoot)
{
static UniqueAccess<std::unordered_map<EmoteId, std::weak_ptr<const Emote>>>
cache_;
auto cache = cache_.access();
auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate =
QString("https:" + jsonRoot.value("urlTemplate").toString());
for (auto jsonEmote_ : jsonEmotes) {
auto jsonEmote = jsonEmote_.toObject();
auto id = EmoteId{jsonEmote.value("id").toString()};
auto name = EmoteName{jsonEmote.value("code").toString()};
// emoteObject.value("imageType").toString();
auto emote = Emote(
{name,
ImageSet{
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
Tooltip{name.string + "<br />Channel Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
auto shared = (*cache)[id].lock();
if (shared && *shared == emote) {
// reuse old shared_ptr if nothing changed
emotes[name] = shared;
} else {
(*cache)[id] = emotes[name] =
std::make_shared<Emote>(std::move(emote));
}
}
return {Success, std::move(emotes)};
}
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{
urlTemplate.detach();
return {urlTemplate.replace("{{id}}", id.string)
.replace("{{image}}", emoteScale)};
}
} // namespace chatterino } // namespace chatterino

View file

@ -6,11 +6,4 @@ class QString;
namespace chatterino { namespace chatterino {
class EmoteMap;
constexpr const char *bttvChannelEmoteApiUrl =
"https://api.betterttv.net/2/channels/";
void loadBttvChannelEmotes(const QString &channelName,
std::function<void(EmoteMap &&)> callback);
} // namespace chatterino } // namespace chatterino

View file

@ -33,6 +33,13 @@ void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
Image::fromUrl(url3x, 0.25)}; Image::fromUrl(url3x, 0.25)};
emoteData.tooltip = {tooltip}; emoteData.tooltip = {tooltip};
} }
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
{
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
static std::mutex mutex;
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
}
std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonObject &jsonRoot, std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonObject &jsonRoot,
const EmoteMap &currentEmotes) const EmoteMap &currentEmotes)
{ {
@ -64,6 +71,36 @@ std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonObject &jsonRoot,
return {Success, std::move(emotes)}; return {Success, std::move(emotes)};
} }
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
{
auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = EmoteMap();
for (auto jsonSet : jsonSets) {
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
for (auto _jsonEmote : jsonEmotes) {
auto jsonEmote = _jsonEmote.toObject();
// margins
auto id = EmoteId{QString::number(jsonEmote.value("id").toInt())};
auto name = EmoteName{jsonEmote.value("name").toString()};
auto urls = jsonEmote.value("urls").toObject();
Emote emote;
fillInEmoteData(urls, name, name.string + "<br/>Channel FFZ Emote",
emote);
emote.homePage =
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
emotes[name] = cachedOrMake(std::move(emote), id);
}
}
return {Success, std::move(emotes)};
}
} // namespace } // namespace
FfzEmotes::FfzEmotes() FfzEmotes::FfzEmotes()
@ -107,66 +144,20 @@ void FfzEmotes::loadGlobal()
void FfzEmotes::loadChannel(const QString &channelName, void FfzEmotes::loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback) std::function<void(EmoteMap &&)> callback)
{ {
// printf("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n", log("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n", channelName);
// qPrintable(channelName));
// QString url("https://api.frankerfacez.com/v1/room/" + channelName); NetworkRequest request("https://api.frankerfacez.com/v1/room/" +
channelName);
request.setCaller(QThread::currentThread());
request.setTimeout(3000);
// NetworkRequest request(url); request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
// request.setCaller(QThread::currentThread()); auto pair = parseChannelEmotes(result.parseJson());
// request.setTimeout(3000); if (pair.first) callback(std::move(pair.second));
// request.onSuccess([this, channelName, _map](auto result) -> Outcome { return pair.first;
// return this->parseChannelEmotes(result.parseJson()); });
//});
// request.execute(); request.execute();
}
Outcome parseChannelEmotes(const QJsonObject &jsonRoot)
{
// auto rootNode = result.parseJson();
// auto map = _map.lock();
// if (_map.expired()) {
// return false;
//}
// map->clear();
// auto setsNode = rootNode.value("sets").toObject();
// std::vector<QString> codes;
// for (const QJsonValue &setNode : setsNode) {
// auto emotesNode = setNode.toObject().value("emoticons").toArray();
// for (const QJsonValue &emoteNode : emotesNode) {
// QJsonObject emoteObject = emoteNode.toObject();
// // margins
// int id = emoteObject.value("id").toInt();
// QString code = emoteObject.value("name").toString();
// QJsonObject urls = emoteObject.value("urls").toObject();
// auto emote = this->channelEmoteCache_.getOrAdd(id, [id, &code,
// &urls] {
// EmoteData emoteData;
// fillInEmoteData(urls, code, code + "<br/>Channel FFZ Emote",
// emoteData); emoteData.pageLink =
// QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
// return emoteData;
// });
// this->channelEmotes.insert(code, emote);
// map->insert(code, emote);
// codes.push_back(code);
// }
// this->channelEmoteCodes[channelName] = codes;
//}
return Success;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,14 +1,12 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include "common/Atomic.hpp" #include "common/Atomic.hpp"
#include "messages/Emote.hpp" #include "messages/Emote.hpp"
#include "messages/EmoteCache.hpp"
namespace chatterino { namespace chatterino {
class FfzEmotes final : std::enable_shared_from_this<FfzEmotes> class FfzEmotes final
{ {
static constexpr const char *globalEmoteApiUrl = static constexpr const char *globalEmoteApiUrl =
"https://api.frankerfacez.com/v1/set/global"; "https://api.frankerfacez.com/v1/set/global";
@ -20,10 +18,9 @@ public:
std::shared_ptr<const EmoteMap> global() const; std::shared_ptr<const EmoteMap> global() const;
boost::optional<EmotePtr> global(const EmoteName &name) const; boost::optional<EmotePtr> global(const EmoteName &name) const;
void loadGlobal(); void loadGlobal();
void loadChannel(const QString &channelName, static void loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback); std::function<void(EmoteMap &&)> callback);
private: private:
Atomic<std::shared_ptr<const EmoteMap>> global_; Atomic<std::shared_ptr<const EmoteMap>> global_;

View file

@ -5,6 +5,7 @@
#include "controllers/accounts/AccountController.hpp" #include "controllers/accounts/AccountController.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "messages/Message.hpp" #include "messages/Message.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/bttv/LoadBttvChannelEmote.hpp" #include "providers/bttv/LoadBttvChannelEmote.hpp"
#include "providers/twitch/PubsubClient.hpp" #include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/TwitchCommon.hpp"
@ -51,6 +52,8 @@ auto parseRecentMessages(const QJsonObject &jsonRoot, TwitchChannel &channel)
TwitchChannel::TwitchChannel(const QString &name) TwitchChannel::TwitchChannel(const QString &name)
: Channel(name, Channel::Type::Twitch) : Channel(name, Channel::Type::Twitch)
, bttvEmotes_(std::make_shared<EmoteMap>())
, ffzEmotes_(std::make_shared<EmoteMap>())
, subscriptionUrl_("https://www.twitch.tv/subs/" + name) , subscriptionUrl_("https://www.twitch.tv/subs/" + name)
, channelUrl_("https://twitch.tv/" + name) , channelUrl_("https://twitch.tv/" + name)
, popoutPlayerUrl_("https://player.twitch.tv/?channel=" + name) , popoutPlayerUrl_("https://player.twitch.tv/?channel=" + name)
@ -111,15 +114,17 @@ bool TwitchChannel::canSendMessage() const
void TwitchChannel::refreshChannelEmotes() void TwitchChannel::refreshChannelEmotes()
{ {
loadBttvChannelEmotes( BttvEmotes::loadChannel(
this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
if (auto shared = weak.lock()) //
*this->bttvEmotes_.access() = emoteMap;
});
getApp()->emotes->ffz.loadChannel(
this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) { this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
if (auto shared = weak.lock()) if (auto shared = weak.lock())
*this->ffzEmotes_.access() = emoteMap; this->bttvEmotes_.set(
std::make_shared<EmoteMap>(std::move(emoteMap)));
});
FfzEmotes::loadChannel(
this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
if (auto shared = weak.lock())
this->ffzEmotes_.set(
std::make_shared<EmoteMap>(std::move(emoteMap)));
}); });
} }
@ -248,7 +253,7 @@ void TwitchChannel::addPartedUser(const QString &user)
} }
} }
QString TwitchChannel::getRoomId() const QString TwitchChannel::roomId() const
{ {
return *this->roomID_.access(); return *this->roomID_.access();
} }
@ -284,47 +289,45 @@ TwitchChannel::accessStreamStatus() const
return this->streamStatus_.accessConst(); return this->streamStatus_.accessConst();
} }
boost::optional<EmotePtr> TwitchChannel::getBttvEmote( boost::optional<EmotePtr> TwitchChannel::bttvEmote(const EmoteName &name) const
const EmoteName &name) const
{ {
auto emotes = this->bttvEmotes_.access(); auto emotes = this->bttvEmotes_.get();
auto it = emotes->find(name); auto it = emotes->find(name);
if (it == emotes->end()) return boost::none; if (it == emotes->end()) return boost::none;
return it->second; return it->second;
} }
boost::optional<EmotePtr> TwitchChannel::getFfzEmote( boost::optional<EmotePtr> TwitchChannel::ffzEmote(const EmoteName &name) const
const EmoteName &name) const
{ {
auto emotes = this->bttvEmotes_.access(); auto emotes = this->bttvEmotes_.get();
auto it = emotes->find(name); auto it = emotes->find(name);
if (it == emotes->end()) return boost::none; if (it == emotes->end()) return boost::none;
return it->second; return it->second;
} }
AccessGuard<const EmoteMap> TwitchChannel::accessBttvEmotes() const std::shared_ptr<const EmoteMap> TwitchChannel::bttvEmotes() const
{ {
return this->bttvEmotes_.accessConst(); return this->bttvEmotes_.get();
} }
AccessGuard<const EmoteMap> TwitchChannel::accessFfzEmotes() const std::shared_ptr<const EmoteMap> TwitchChannel::ffzEmotes() const
{ {
return this->ffzEmotes_.accessConst(); return this->ffzEmotes_.get();
} }
const QString &TwitchChannel::getSubscriptionUrl() const QString &TwitchChannel::subscriptionUrl()
{ {
return this->subscriptionUrl_; return this->subscriptionUrl_;
} }
const QString &TwitchChannel::getChannelUrl() const QString &TwitchChannel::channelUrl()
{ {
return this->channelUrl_; return this->channelUrl_;
} }
const QString &TwitchChannel::getPopoutPlayerUrl() const QString &TwitchChannel::popoutPlayerUrl()
{ {
return this->popoutPlayerUrl_; return this->popoutPlayerUrl_;
} }
@ -347,7 +350,7 @@ void TwitchChannel::setLive(bool newLiveStatus)
void TwitchChannel::refreshLiveStatus() void TwitchChannel::refreshLiveStatus()
{ {
auto roomID = this->getRoomId(); auto roomID = this->roomId();
if (roomID.isEmpty()) { if (roomID.isEmpty()) {
log("[TwitchChannel:{}] Refreshing live status (Missing ID)", log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
@ -459,7 +462,7 @@ void TwitchChannel::loadRecentMessages()
"https://tmi.twitch.tv/api/rooms/%1/recent_messages?client_id=" + "https://tmi.twitch.tv/api/rooms/%1/recent_messages?client_id=" +
getDefaultClientID(); getDefaultClientID();
NetworkRequest request(genericURL.arg(this->getRoomId())); NetworkRequest request(genericURL.arg(this->roomId()));
request.makeAuthorizedV5(getDefaultClientID()); request.makeAuthorizedV5(getDefaultClientID());
request.setCaller(QThread::currentThread()); request.setCaller(QThread::currentThread());
// can't be concurrent right now due to SignalVector // can't be concurrent right now due to SignalVector
@ -483,7 +486,7 @@ void TwitchChannel::refreshPubsub()
{ {
// listen to moderation actions // listen to moderation actions
if (!this->hasModRights()) return; if (!this->hasModRights()) return;
auto roomId = this->getRoomId(); auto roomId = this->roomId();
if (roomId.isEmpty()) return; if (roomId.isEmpty()) return;
auto account = getApp()->accounts->twitch.getCurrent(); auto account = getApp()->accounts->twitch.getCurrent();
@ -541,7 +544,7 @@ Outcome TwitchChannel::parseViewerList(const QJsonObject &jsonRoot)
void TwitchChannel::loadBadges() void TwitchChannel::loadBadges()
{ {
auto url = Url{"https://badges.twitch.tv/v1/badges/channels/" + auto url = Url{"https://badges.twitch.tv/v1/badges/channels/" +
this->getRoomId() + "/display?language=en"}; this->roomId() + "/display?language=en"};
NetworkRequest req(url.string); NetworkRequest req(url.string);
req.setCaller(QThread::currentThread()); req.setCaller(QThread::currentThread());

View file

@ -2,9 +2,9 @@
#include <IrcConnection> #include <IrcConnection>
#include "common/Atomic.hpp"
#include "common/Channel.hpp" #include "common/Channel.hpp"
#include "common/Common.hpp" #include "common/Common.hpp"
#include "common/Atomic.hpp"
#include "common/UniqueAccess.hpp" #include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp" #include "messages/Emote.hpp"
#include "singletons/Emotes.hpp" #include "singletons/Emotes.hpp"
@ -64,19 +64,19 @@ public:
void setMod(bool value); void setMod(bool value);
virtual bool isBroadcaster() const override; virtual bool isBroadcaster() const override;
QString getRoomId() const; QString roomId() const;
void setRoomId(const QString &id); void setRoomId(const QString &id);
AccessGuard<const RoomModes> accessRoomModes() const; AccessGuard<const RoomModes> accessRoomModes() const;
void setRoomModes(const RoomModes &roomModes_); void setRoomModes(const RoomModes &roomModes_);
AccessGuard<const StreamStatus> accessStreamStatus() const; AccessGuard<const StreamStatus> accessStreamStatus() const;
boost::optional<EmotePtr> getBttvEmote(const EmoteName &name) const; boost::optional<EmotePtr> bttvEmote(const EmoteName &name) const;
boost::optional<EmotePtr> getFfzEmote(const EmoteName &name) const; boost::optional<EmotePtr> ffzEmote(const EmoteName &name) const;
AccessGuard<const EmoteMap> accessBttvEmotes() const; std::shared_ptr<const EmoteMap> bttvEmotes() const;
AccessGuard<const EmoteMap> accessFfzEmotes() const; std::shared_ptr<const EmoteMap> ffzEmotes() const;
const QString &getSubscriptionUrl(); const QString &subscriptionUrl();
const QString &getChannelUrl(); const QString &channelUrl();
const QString &getPopoutPlayerUrl(); const QString &popoutPlayerUrl();
boost::optional<EmotePtr> getTwitchBadge(const QString &set, boost::optional<EmotePtr> getTwitchBadge(const QString &set,
const QString &version) const; const QString &version) const;
@ -127,8 +127,8 @@ private:
UniqueAccess<UserState> userState_; UniqueAccess<UserState> userState_;
UniqueAccess<RoomModes> roomModes_; UniqueAccess<RoomModes> roomModes_;
UniqueAccess<EmoteMap> bttvEmotes_; Atomic<std::shared_ptr<const EmoteMap>> bttvEmotes_;
UniqueAccess<EmoteMap> ffzEmotes_; Atomic<std::shared_ptr<const EmoteMap>> ffzEmotes_;
const QString subscriptionUrl_; const QString subscriptionUrl_;
const QString channelUrl_; const QString channelUrl_;
const QString popoutPlayerUrl_; const QString popoutPlayerUrl_;

View file

@ -341,7 +341,7 @@ void TwitchMessageBuilder::parseRoomID()
if (iterator != std::end(this->tags)) { if (iterator != std::end(this->tags)) {
this->roomID_ = iterator.value().toString(); this->roomID_ = iterator.value().toString();
if (this->twitchChannel->getRoomId().isEmpty()) { if (this->twitchChannel->roomId().isEmpty()) {
this->twitchChannel->setRoomId(this->roomID_); this->twitchChannel->setRoomId(this->roomID_);
} }
} }
@ -641,12 +641,12 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
if ((emote = getApp()->emotes->bttv.global(name))) { if ((emote = getApp()->emotes->bttv.global(name))) {
flags = MessageElementFlag::BttvEmote; flags = MessageElementFlag::BttvEmote;
} else if (twitchChannel && } else if (twitchChannel &&
(emote = this->twitchChannel->getBttvEmote(name))) { (emote = this->twitchChannel->bttvEmote(name))) {
flags = MessageElementFlag::BttvEmote; flags = MessageElementFlag::BttvEmote;
} else if ((emote = getApp()->emotes->ffz.global(name))) { } else if ((emote = getApp()->emotes->ffz.global(name))) {
flags = MessageElementFlag::FfzEmote; flags = MessageElementFlag::FfzEmote;
} else if (twitchChannel && } else if (twitchChannel &&
(emote = this->twitchChannel->getFfzEmote(name))) { (emote = this->twitchChannel->ffzEmote(name))) {
flags = MessageElementFlag::FfzEmote; flags = MessageElementFlag::FfzEmote;
} }

View file

@ -79,10 +79,12 @@ void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName) std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName)
{ {
TwitchChannel *channel = new TwitchChannel(channelName); auto channel =
std::shared_ptr<TwitchChannel>(new TwitchChannel(channelName));
channel->refreshChannelEmotes();
channel->sendMessageSignal.connect( channel->sendMessageSignal.connect(
[this, channel](auto &chan, auto &msg, bool &sent) { [this, channel = channel.get()](auto &chan, auto &msg, bool &sent) {
this->onMessageSendRequested(channel, msg, sent); this->onMessageSendRequested(channel, msg, sent);
}); });
@ -175,7 +177,7 @@ std::shared_ptr<Channel> TwitchServer::getChannelOrEmptyByID(
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel); auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel);
if (!twitchChannel) continue; if (!twitchChannel) continue;
if (twitchChannel->getRoomId() == channelId) { if (twitchChannel->roomId() == channelId) {
return twitchChannel; return twitchChannel;
} }
} }

View file

@ -74,6 +74,8 @@ EmotePopup::EmotePopup()
layout->addWidget(notebook); layout->addWidget(notebook);
layout->setMargin(0); layout->setMargin(0);
auto clicked = [this](const Link &link) { this->linkClicked.invoke(link); };
auto makeView = [&](QString tabTitle) { auto makeView = [&](QString tabTitle) {
auto view = new ChannelView(); auto view = new ChannelView();
@ -82,6 +84,7 @@ EmotePopup::EmotePopup()
MessageElementFlag::EmoteImages}); MessageElementFlag::EmoteImages});
view->setEnableScrollingToBottom(false); view->setEnableScrollingToBottom(false);
notebook->addPage(view, tabTitle); notebook->addPage(view, tabTitle);
view->linkClicked.connect(clicked);
return view; return view;
}; };
@ -92,11 +95,6 @@ EmotePopup::EmotePopup()
this->viewEmojis_ = makeView("Emojis"); this->viewEmojis_ = makeView("Emojis");
this->loadEmojis(); this->loadEmojis();
this->globalEmotesView_->linkClicked.connect(
[this](const Link &link) { this->linkClicked.invoke(link); });
this->viewEmojis_->linkClicked.connect(
[this](const Link &link) { this->linkClicked.invoke(link); });
} }
void EmotePopup::loadChannel(ChannelPtr _channel) void EmotePopup::loadChannel(ChannelPtr _channel)
@ -128,10 +126,8 @@ void EmotePopup::loadChannel(ChannelPtr _channel)
addEmotes(*globalChannel, *getApp()->emotes->ffz.global(), "FrankerFaceZ"); addEmotes(*globalChannel, *getApp()->emotes->ffz.global(), "FrankerFaceZ");
// channel // channel
// addEmotes(*channel->accessBttvEmotes(), "BetterTTV Channel Emotes", addEmotes(*channelChannel, *twitchChannel->bttvEmotes(), "BetterTTV");
// "BetterTTV Channel Emote"); addEmotes(*channelChannel, *twitchChannel->ffzEmotes(), "FrankerFaceZ");
// addEmotes(*channel->accessFfzEmotes(), "FrankerFaceZ Channel Emotes",
// "FrankerFaceZ Channel Emote");
this->globalEmotesView_->setChannel(globalChannel); this->globalEmotesView_->setChannel(globalChannel);
this->subEmotesView_->setChannel(subChannel); this->subEmotesView_->setChannel(subChannel);