categorized emtotepopup

This commit is contained in:
fourtf 2018-08-11 14:20:53 +02:00
parent 09b8a9d821
commit c719bb6b74
55 changed files with 420 additions and 604 deletions

View file

@ -233,7 +233,6 @@ SOURCES += \
src/providers/twitch/PubsubClient.cpp \ src/providers/twitch/PubsubClient.cpp \
src/providers/twitch/TwitchApi.cpp \ src/providers/twitch/TwitchApi.cpp \
src/messages/Emote.cpp \ src/messages/Emote.cpp \
src/messages/EmoteMap.cpp \
src/messages/ImageSet.cpp \ src/messages/ImageSet.cpp \
src/providers/bttv/BttvEmotes.cpp \ src/providers/bttv/BttvEmotes.cpp \
src/providers/ffz/FfzEmotes.cpp \ src/providers/ffz/FfzEmotes.cpp \
@ -250,7 +249,8 @@ SOURCES += \
src/util/FunctionEventFilter.cpp \ src/util/FunctionEventFilter.cpp \
src/widgets/helper/EffectLabel.cpp \ src/widgets/helper/EffectLabel.cpp \
src/widgets/helper/Button.cpp \ src/widgets/helper/Button.cpp \
src/messages/MessageContainer.cpp src/messages/MessageContainer.cpp \
src/debug/Benchmark.cpp
HEADERS += \ HEADERS += \
src/Application.hpp \ src/Application.hpp \
@ -258,7 +258,7 @@ HEADERS += \
src/common/Common.hpp \ src/common/Common.hpp \
src/common/CompletionModel.hpp \ src/common/CompletionModel.hpp \
src/common/FlagsEnum.hpp \ src/common/FlagsEnum.hpp \
src/common/MutexValue.hpp \ src/common/Atomic.hpp \
src/common/NetworkCommon.hpp \ src/common/NetworkCommon.hpp \
src/common/NetworkData.hpp \ src/common/NetworkData.hpp \
src/common/NetworkManager.hpp \ src/common/NetworkManager.hpp \
@ -305,7 +305,6 @@ HEADERS += \
src/messages/MessageBuilder.hpp \ src/messages/MessageBuilder.hpp \
src/messages/MessageColor.hpp \ src/messages/MessageColor.hpp \
src/messages/MessageElement.hpp \ src/messages/MessageElement.hpp \
src/messages/MessageParseArgs.hpp \
src/messages/Selection.hpp \ src/messages/Selection.hpp \
src/PrecompiledHeader.hpp \ src/PrecompiledHeader.hpp \
src/providers/emoji/Emojis.hpp \ src/providers/emoji/Emojis.hpp \
@ -414,7 +413,6 @@ HEADERS += \
src/singletons/Updates.hpp \ src/singletons/Updates.hpp \
src/singletons/NativeMessaging.hpp \ src/singletons/NativeMessaging.hpp \
src/singletons/Theme.hpp \ src/singletons/Theme.hpp \
src/common/SimpleSignalVector.hpp \
src/common/SignalVector.hpp \ src/common/SignalVector.hpp \
src/widgets/dialogs/LogsPopup.hpp \ src/widgets/dialogs/LogsPopup.hpp \
src/common/Singleton.hpp \ src/common/Singleton.hpp \
@ -427,7 +425,6 @@ HEADERS += \
src/providers/twitch/PubsubClient.hpp \ src/providers/twitch/PubsubClient.hpp \
src/providers/twitch/TwitchApi.hpp \ src/providers/twitch/TwitchApi.hpp \
src/messages/Emote.hpp \ src/messages/Emote.hpp \
src/messages/EmoteMap.hpp \
src/messages/EmoteCache.hpp \ src/messages/EmoteCache.hpp \
src/messages/ImageSet.hpp \ src/messages/ImageSet.hpp \
src/common/Outcome.hpp \ src/common/Outcome.hpp \

View file

@ -113,11 +113,11 @@ void Application::initNm()
void Application::initPubsub() void Application::initPubsub()
{ {
this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) { this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) {
Log("WHISPER SENT LOL"); // log("WHISPER SENT LOL"); //
}); });
this->twitch.pubsub->signals_.whisper.received.connect([](const auto &msg) { this->twitch.pubsub->signals_.whisper.received.connect([](const auto &msg) {
Log("WHISPER RECEIVED LOL"); // log("WHISPER RECEIVED LOL"); //
}); });
this->twitch.pubsub->signals_.moderation.chatCleared.connect( this->twitch.pubsub->signals_.moderation.chatCleared.connect(

View file

@ -6,14 +6,14 @@
namespace chatterino { namespace chatterino {
template <typename T> template <typename T>
class MutexValue : boost::noncopyable class Atomic : boost::noncopyable
{ {
public: public:
MutexValue() Atomic()
{ {
} }
MutexValue(T &&val) Atomic(T &&val)
: value_(val) : value_(val)
{ {
} }
@ -32,6 +32,13 @@ public:
this->value_ = val; this->value_ = val;
} }
void set(T &&val)
{
std::lock_guard<std::mutex> guard(this->mutex_);
this->value_ = std::move(val);
}
private: private:
mutable std::mutex mutex_; mutable std::mutex mutex_;
T value_; T value_;

View file

@ -104,7 +104,7 @@ int CompletionModel::rowCount(const QModelIndex &) const
void CompletionModel::refresh() void CompletionModel::refresh()
{ {
Log("[CompletionModel:{}] Refreshing...]", this->channelName_); log("[CompletionModel:{}] Refreshing...]", this->channelName_);
auto app = getApp(); auto app = getApp();

View file

@ -136,7 +136,7 @@ void NetworkRequest::execute()
} break; } break;
default: { default: {
Log("[Execute] Unhandled request type"); log("[Execute] Unhandled request type");
} break; } break;
} }
} }
@ -190,10 +190,11 @@ void NetworkRequest::doRequest()
case NetworkRequestType::Put: case NetworkRequestType::Put:
return NetworkManager::accessManager.put(data->request_, return NetworkManager::accessManager.put(data->request_,
data->payload_); data->payload_);
case NetworkRequestType::Delete: case NetworkRequestType::Delete:
return NetworkManager::accessManager.deleteResource(data->request_); return NetworkManager::accessManager.deleteResource(
data->request_);
default: default:
return nullptr; return nullptr;
@ -201,13 +202,13 @@ void NetworkRequest::doRequest()
}(); }();
if (reply == nullptr) { if (reply == nullptr) {
Log("Unhandled request type"); log("Unhandled request type");
return; return;
} }
if (timer->isStarted()) { if (timer->isStarted()) {
timer->onTimeout(worker, [reply, data]() { timer->onTimeout(worker, [reply, data]() {
Log("Aborted!"); log("Aborted!");
reply->abort(); reply->abort();
if (data->onError_) { if (data->onError_) {
data->onError_(-2); data->onError_(-2);
@ -234,7 +235,7 @@ void NetworkRequest::doRequest()
NetworkResult result(bytes); NetworkResult result(bytes);
DebugCount::increase("http request success"); DebugCount::increase("http request success");
Log("starting {}", data->request_.url().toString()); // log("starting {}", data->request_.url().toString());
if (data->onSuccess_) { if (data->onSuccess_) {
if (data->executeConcurrently) if (data->executeConcurrently)
QtConcurrent::run( QtConcurrent::run(
@ -243,7 +244,7 @@ void NetworkRequest::doRequest()
else else
data->onSuccess_(result); data->onSuccess_(result);
} }
Log("finished {}", data->request_.url().toString()); // log("finished {}", data->request_.url().toString());
reply->deleteLater(); reply->deleteLater();
}; };

View file

@ -31,7 +31,7 @@ rapidjson::Document NetworkResult::parseRapidJson() const
ret.Parse(this->data_.data(), this->data_.length()); ret.Parse(this->data_.data(), this->data_.length());
if (result.Code() != rapidjson::kParseErrorNone) { if (result.Code() != rapidjson::kParseErrorNone) {
Log("JSON parse error: {} ({})", log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset()); rapidjson::GetParseError_En(result.Code()), result.Offset());
return ret; return ret;
} }

View file

@ -1,34 +0,0 @@
#pragma once
#include <pajlada/signals/signal.hpp>
#include <mutex>
#include <vector>
namespace chatterino {
template <typename TValue>
class SimpleSignalVector
{
public:
SimpleSignalVector &operator=(std::vector<TValue> &other)
{
this->data_ = other;
this->updated.invoke();
return *this;
}
operator std::vector<TValue> &()
{
return this->data_;
}
pajlada::Signals::NoArgSignal updated;
private:
std::vector<TValue> data_;
};
} // namespace chatterino

View file

@ -46,16 +46,15 @@ public:
} }
private: private:
T *element_; T *element_{};
std::mutex *mutex_; std::mutex *mutex_{};
bool isValid_ = true; bool isValid_{true};
}; };
template <typename T> template <typename T>
class UniqueAccess class UniqueAccess
{ {
public: public:
// template <typename X = decltype(T())>
UniqueAccess() UniqueAccess()
: element_(T()) : element_(T())
{ {

View file

@ -78,7 +78,7 @@ void CommandController::save()
{ {
QFile textFile(this->filePath_); QFile textFile(this->filePath_);
if (!textFile.open(QIODevice::WriteOnly)) { if (!textFile.open(QIODevice::WriteOnly)) {
Log("[CommandController::saveCommands] Unable to open {} for writing", log("[CommandController::saveCommands] Unable to open {} for writing",
this->filePath_); this->filePath_);
return; return;
} }

21
src/debug/Benchmark.cpp Normal file
View file

@ -0,0 +1,21 @@
#include "Benchmark.hpp"
namespace chatterino {
BenchmarkGuard::BenchmarkGuard(const QString &_name)
: name_(_name)
{
timer_.start();
}
BenchmarkGuard::~BenchmarkGuard()
{
log("{} {} ms", this->name_, float(timer_.nsecsElapsed()) / 1000000.0f);
}
qreal BenchmarkGuard::getElapsedMs()
{
return qreal(timer_.nsecsElapsed()) / 1000000.0;
}
} // namespace chatterino

View file

@ -3,40 +3,20 @@
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include <QElapsedTimer> #include <QElapsedTimer>
#include <boost/current_function.hpp>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#define BENCH(x) \
QElapsedTimer x; \
x.start();
#define MARK(x) \
qDebug() << BOOST_CURRENT_FUNCTION << __LINE__ \
<< static_cast<float>(x.nsecsElapsed()) / 1000000.0 << "ms";
namespace chatterino { namespace chatterino {
class BenchmarkGuard : boost::noncopyable class BenchmarkGuard : boost::noncopyable
{ {
QElapsedTimer timer;
QString name;
public: public:
BenchmarkGuard(const QString &_name) BenchmarkGuard(const QString &_name);
: name(_name) ~BenchmarkGuard();
{ qreal getElapsedMs();
timer.start();
}
~BenchmarkGuard() private:
{ QElapsedTimer timer_;
Log("{} {} ms", this->name, float(timer.nsecsElapsed()) / 1000000.0f); QString name_;
}
qreal getElapsedMs()
{
return qreal(timer.nsecsElapsed()) / 1000000.0;
}
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -8,17 +8,22 @@
namespace chatterino { namespace chatterino {
template <typename... Args> template <typename... Args>
inline void Log(const std::string &formatString, Args &&... args) inline void log(const std::string &formatString, Args &&... args)
{ {
qDebug().noquote() << QTime::currentTime().toString("hh:mm:ss.zzz") qDebug().noquote() << QTime::currentTime().toString("hh:mm:ss.zzz")
<< fS(formatString, std::forward<Args>(args)...).c_str(); << fS(formatString, std::forward<Args>(args)...).c_str();
} }
template <typename... Args> template <typename... Args>
inline void Warn(const std::string &formatString, Args &&... args) inline void log(const char *formatString, Args &&... args)
{ {
qWarning() << QTime::currentTime().toString("hh:mm:ss.zzz") log(std::string(formatString), std::forward<Args>(args)...);
<< fS(formatString, std::forward<Args>(args)...).c_str(); }
template <typename... Args>
inline void log(const QString &formatString, Args &&... args)
{
log(formatString.toStdString(), std::forward<Args>(args)...);
} }
} // namespace chatterino } // namespace chatterino

View file

@ -5,13 +5,15 @@
#include <QApplication> #include <QApplication>
#include <QStringList> #include <QStringList>
#include <memory>
#include <messages/Image.hpp>
using namespace chatterino; using namespace chatterino;
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
auto shared = std::make_shared<QString>();
log(std::atomic_is_lock_free(&shared));
QApplication a(argc, argv); QApplication a(argc, argv);
// convert char** to QStringList // convert char** to QStringList

View file

@ -15,61 +15,13 @@ bool operator!=(const Emote &a, const Emote &b)
return !(a == b); return !(a == b);
} }
// EmotePtr Emote::create(const EmoteData2 &data) EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache)
//{ {
//} // reuse old shared_ptr if nothing changed
auto it = cache.find(emote.name);
if (it != cache.end() && *it->second == emote) return it->second;
// EmotePtr Emote::create(EmoteData2 &&data) return std::make_shared<Emote>(std::move(emote));
//{ }
//}
// Emote::Emote(EmoteData2 &&data)
// : data_(data)
//{
//}
//
// Emote::Emote(const EmoteData2 &data)
// : data_(data)
//{
//}
//
// const Url &Emote::getHomePage() const
//{
// return this->data_.homePage;
//}
//
// const EmoteName &Emote::getName() const
//{
// return this->data_.name;
//}
//
// const Tooltip &Emote::getTooltip() const
//{
// return this->data_.tooltip;
//}
//
// const ImageSet &Emote::getImages() const
//{
// return this->data_.images;
//}
//
// const QString &Emote::getCopyString() const
//{
// return this->data_.name.string;
//}
//
// bool Emote::operator==(const Emote &other) const
//{
// auto &a = this->data_;
// auto &b = other.data_;
//
// return std::tie(a.homePage, a.name, a.tooltip, a.images) ==
// std::tie(b.homePage, b.name, b.tooltip, b.images);
//}
//
// bool Emote::operator!=(const Emote &other) const
//{
// return !this->operator==(other);
//}
} // namespace chatterino } // namespace chatterino

View file

@ -37,4 +37,6 @@ using EmoteIdMap = std::unordered_map<EmoteId, EmotePtr>;
using WeakEmoteMap = std::unordered_map<EmoteName, std::weak_ptr<const Emote>>; 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);
} // namespace chatterino } // namespace chatterino

View file

@ -1,44 +0,0 @@
#include "EmoteMap.hpp"
#include "Application.hpp"
#include "singletons/Settings.hpp"
namespace chatterino {
// EmoteData::EmoteData(Image *image)
// : image1x(image)
//{
//}
//// Emotes must have a 1x image to be valid
// bool EmoteData::isValid() const
//{
// return this->image1x != nullptr;
//}
// Image *EmoteData::getImage(float scale) const
//{
// int quality = getApp()->settings->preferredEmoteQuality;
// if (quality == 0) {
// scale *= getApp()->settings->emoteScale.getValue();
// quality = [&] {
// if (scale <= 1) return 1;
// if (scale <= 2) return 2;
// return 3;
// }();
// }
// Image *_image;
// if (quality == 3 && this->image3x != nullptr) {
// _image = this->image3x;
// } else if (quality >= 2 && this->image2x != nullptr) {
// _image = this->image2x;
// } else {
// _image = this->image1x;
// }
// return _image;
//}
} // namespace chatterino

View file

@ -1,20 +0,0 @@
#pragma once
#include "boost/optional.hpp"
#include "messages/Emote.hpp"
namespace chatterino {
// class EmoteMap
//{
// public:
// void add(Emote emote);
// void remove(const Emote &emote);
// void remove(const QString &name);
// private:
//};
// using EmoteMap = ConcurrentMap<QString, EmoteData>;
} // namespace chatterino

View file

@ -78,7 +78,7 @@ QVector<Frame<QImage>> readFrames(QImageReader &reader, const Url &url)
QVector<Frame<QImage>> frames; QVector<Frame<QImage>> frames;
if (reader.imageCount() == 0) { if (reader.imageCount() == 0) {
Log("Error while reading image {}: '{}'", url.string, log("Error while reading image {}: '{}'", url.string,
reader.errorString()); reader.errorString());
return frames; return frames;
} }
@ -94,7 +94,7 @@ QVector<Frame<QImage>> readFrames(QImageReader &reader, const Url &url)
} }
if (frames.size() == 0) { if (frames.size() == 0) {
Log("Error while reading image {}: '{}'", url.string, log("Error while reading image {}: '{}'", url.string,
reader.errorString()); reader.errorString());
} }
@ -200,7 +200,7 @@ Image::Image(const Url &url, qreal scale)
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}}) , frames_(QVector<Frame<QPixmap>>{Frame<QPixmap>{pixmap, 1}})
{ {
} }

View file

@ -16,6 +16,14 @@ const TimeoutMessageTag timeoutMessage{};
MessagePtr makeSystemMessage(const QString &text); MessagePtr makeSystemMessage(const QString &text);
struct MessageParseArgs {
bool disablePingSounds = false;
bool isReceivedWhisper = false;
bool isSentWhisper = false;
bool trimSubscriberUsername = false;
bool isStaffOrBroadcaster = false;
};
class MessageBuilder class MessageBuilder
{ {
public: public:

View file

@ -2,12 +2,5 @@
namespace chatterino { namespace chatterino {
struct MessageParseArgs {
bool disablePingSounds = false;
bool isReceivedWhisper = false;
bool isSentWhisper = false;
bool trimSubscriberUsername = false;
bool isStaffOrBroadcaster = false;
};
} // namespace chatterino } // namespace chatterino

View file

@ -35,7 +35,7 @@ struct SelectionItem {
bool operator>(const SelectionItem &b) const bool operator>(const SelectionItem &b) const
{ {
return b.operator<(*this); return this->operator!=(b) && b.operator<(*this);
} }
bool operator==(const SelectionItem &b) const bool operator==(const SelectionItem &b) const

View file

@ -31,13 +31,10 @@ public:
const Message *getMessage(); const Message *getMessage();
// Height
int getHeight() const; int getHeight() const;
// Flags
MessageLayoutFlags flags; MessageLayoutFlags flags;
// Layout
bool layout(int width, float scale_, MessageElementFlags flags); bool layout(int width, float scale_, MessageElementFlags flags);
// Painting // Painting

View file

@ -126,9 +126,10 @@ void MessageLayoutContainer::breakLine()
int xOffset = 0; int xOffset = 0;
if (this->flags_.has(MessageFlag::Centered) && this->elements_.size() > 0) { if (this->flags_.has(MessageFlag::Centered) && this->elements_.size() > 0) {
xOffset = (width_ - this->elements_.at(this->elements_.size() - 1) xOffset = (width_ - this->elements_.at(0)->getRect().left() -
->getRect() this->elements_.at(this->elements_.size() - 1)
.right()) / ->getRect()
.right()) /
2; 2;
} }

View file

@ -10,9 +10,7 @@
#include <QThread> #include <QThread>
namespace chatterino { namespace chatterino {
namespace { namespace {
Url getEmoteLink(QString urlTemplate, const EmoteId &id, Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale) const QString &emoteScale)
{ {
@ -21,72 +19,14 @@ Url getEmoteLink(QString urlTemplate, const EmoteId &id,
return {urlTemplate.replace("{{id}}", id.string) return {urlTemplate.replace("{{id}}", id.string)
.replace("{{image}}", emoteScale)}; .replace("{{image}}", emoteScale)};
} }
std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonObject &jsonRoot,
} // namespace const EmoteMap &currentEmotes)
AccessGuard<const EmoteMap> BttvEmotes::accessGlobalEmotes() const
{
return this->globalEmotes_.accessConst();
}
boost::optional<EmotePtr> BttvEmotes::getGlobalEmote(const EmoteName &name)
{
auto emotes = this->globalEmotes_.access();
auto it = emotes->find(name);
if (it == emotes->end()) return boost::none;
return it->second;
}
// FOURTF: never returns anything
// boost::optional<EmotePtr> BttvEmotes::getEmote(const EmoteId &id)
//{
// auto cache = this->channelEmoteCache_.access();
// auto it = cache->find(id);
//
// if (it != cache->end()) {
// auto shared = it->second.lock();
// if (shared) {
// return shared;
// }
// }
//
// return boost::none;
//}
void BttvEmotes::loadGlobalEmotes()
{
auto request = NetworkRequest(QString(globalEmoteApiUrl));
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
request.onSuccess([this](auto result) -> Outcome {
// if (auto shared = weak.lock()) {
auto currentEmotes = this->globalEmotes_.access();
auto pair = this->parseGlobalEmotes(result.parseJson(), *currentEmotes);
if (pair.first) {
*currentEmotes = std::move(pair.second);
}
return pair.first;
// }
return Failure;
});
request.execute();
}
std::pair<Outcome, EmoteMap> BttvEmotes::parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes)
{ {
auto emotes = EmoteMap(); auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray(); auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate = auto urlTemplate = qS("https:") + jsonRoot.value("urlTemplate").toString();
QString("https:" + jsonRoot.value("urlTemplate").toString());
for (const QJsonValue &jsonEmote : jsonEmotes) { for (auto jsonEmote : jsonEmotes) {
auto id = EmoteId{jsonEmote.toObject().value("id").toString()}; auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
auto name = EmoteName{jsonEmote.toObject().value("code").toString()}; auto name = EmoteName{jsonEmote.toObject().value("code").toString()};
@ -99,16 +39,49 @@ std::pair<Outcome, EmoteMap> BttvEmotes::parseGlobalEmotes(
Tooltip{name.string + "<br />Global Bttv Emote"}, Tooltip{name.string + "<br />Global Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}}); Url{"https://manage.betterttv.net/emotes/" + id.string}});
auto it = currentEmotes.find(name); emotes[name] = cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
if (it != currentEmotes.end() && *it->second == emote) {
// reuse old shared_ptr if nothing changed
emotes[name] = it->second;
} else {
emotes[name] = std::make_shared<Emote>(std::move(emote));
}
} }
return {Success, std::move(emotes)}; return {Success, std::move(emotes)};
} }
} // namespace
BttvEmotes::BttvEmotes()
: global_(std::make_shared<EmoteMap>())
{
}
std::shared_ptr<const EmoteMap> BttvEmotes::global() const
{
return this->global_.get();
}
boost::optional<EmotePtr> BttvEmotes::global(const EmoteName &name) const
{
auto emotes = this->global_.get();
auto it = emotes->find(name);
if (it == emotes->end()) return boost::none;
return it->second;
}
void BttvEmotes::loadGlobal()
{
auto request = NetworkRequest(QString(globalEmoteApiUrl));
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
request.onSuccess([this](auto result) -> Outcome {
auto emotes = this->global_.get();
auto pair = parseGlobalEmotes(result.parseJson(), *emotes);
if (pair.first)
this->global_.set(
std::make_shared<EmoteMap>(std::move(pair.second)));
return pair.first;
});
request.execute();
}
} // namespace chatterino } // namespace chatterino

View file

@ -1,33 +1,25 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include "common/Atomic.hpp"
#include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp" #include "messages/Emote.hpp"
#include "messages/EmoteCache.hpp"
namespace chatterino { namespace chatterino {
class BttvEmotes final : std::enable_shared_from_this<BttvEmotes> 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";
public: public:
// BttvEmotes(); BttvEmotes();
AccessGuard<const EmoteMap> accessGlobalEmotes() const; std::shared_ptr<const EmoteMap> global() const;
boost::optional<EmotePtr> getGlobalEmote(const EmoteName &name); boost::optional<EmotePtr> global(const EmoteName &name) const;
boost::optional<EmotePtr> getEmote(const EmoteId &id); void loadGlobal();
void loadGlobalEmotes();
private: private:
std::pair<Outcome, EmoteMap> parseGlobalEmotes( Atomic<std::shared_ptr<const EmoteMap>> global_;
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes);
UniqueAccess<EmoteMap> globalEmotes_;
// UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -118,7 +118,7 @@ void Emojis::loadEmojis()
rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length()); rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length());
if (result.Code() != rapidjson::kParseErrorNone) { if (result.Code() != rapidjson::kParseErrorNone) {
Log("JSON parse error: {} ({})", log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset()); rapidjson::GetParseError_En(result.Code()), result.Offset());
return; return;
} }
@ -146,7 +146,7 @@ void Emojis::loadEmojis()
auto toneNameIt = toneNames.find(tone); auto toneNameIt = toneNames.find(tone);
if (toneNameIt == toneNames.end()) { if (toneNameIt == toneNames.end()) {
Log("Tone with key {} does not exist in tone names map", log("Tone with key {} does not exist in tone names map",
tone); tone);
continue; continue;
} }
@ -219,7 +219,7 @@ void Emojis::loadEmojiSet()
auto app = getApp(); auto app = getApp();
app->settings->emojiSet.connect([=](const auto &emojiSet, auto) { app->settings->emojiSet.connect([=](const auto &emojiSet, auto) {
Log("Using emoji set {}", emojiSet); log("Using emoji set {}", emojiSet);
this->emojis.each([=](const auto &name, this->emojis.each([=](const auto &name,
std::shared_ptr<EmojiData> &emoji) { std::shared_ptr<EmojiData> &emoji) {
QString emojiSetToUse = emojiSet; QString emojiSetToUse = emojiSet;

View file

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "common/SimpleSignalVector.hpp"
#include "messages/Emote.hpp" #include "messages/Emote.hpp"
#include "util/ConcurrentMap.hpp" #include "util/ConcurrentMap.hpp"

View file

@ -19,7 +19,6 @@ Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
return {"https:" + emote.toString()}; return {"https:" + emote.toString()};
} }
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name, void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
const QString &tooltip, Emote &emoteData) const QString &tooltip, Emote &emoteData)
{ {
@ -34,52 +33,11 @@ void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
Image::fromUrl(url3x, 0.25)}; Image::fromUrl(url3x, 0.25)};
emoteData.tooltip = {tooltip}; emoteData.tooltip = {tooltip};
} }
} // namespace std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonObject &jsonRoot,
const EmoteMap &currentEmotes)
AccessGuard<const EmoteCache<EmoteName>> FfzEmotes::accessGlobalEmotes() const
{
return this->globalEmotes_.accessConst();
}
boost::optional<EmotePtr> FfzEmotes::getEmote(const EmoteId &id)
{
auto cache = this->channelEmoteCache_.access();
auto it = cache->find(id);
if (it != cache->end()) {
auto shared = it->second.lock();
if (shared) {
return shared;
}
}
return boost::none;
}
boost::optional<EmotePtr> FfzEmotes::getGlobalEmote(const EmoteName &name)
{
return this->globalEmotes_.access()->get(name);
}
void FfzEmotes::loadGlobalEmotes()
{
QString url("https://api.frankerfacez.com/v1/set/global");
NetworkRequest request(url);
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
request.onSuccess([this](auto result) -> Outcome {
return this->parseGlobalEmotes(result.parseJson());
});
request.execute();
}
Outcome FfzEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot)
{ {
auto jsonSets = jsonRoot.value("sets").toObject(); auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = this->globalEmotes_.access(); auto emotes = EmoteMap();
auto replacement = emotes->makeReplacment();
for (auto jsonSet : jsonSets) { for (auto jsonSet : jsonSets) {
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray(); auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
@ -99,15 +57,55 @@ Outcome FfzEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot)
.arg(id.string) .arg(id.string)
.arg(name.string)}; .arg(name.string)};
replacement.add(name, emote); emotes[name] =
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
} }
} }
return Success; return {Success, std::move(emotes)};
}
} // namespace
FfzEmotes::FfzEmotes()
: global_(std::make_shared<EmoteMap>())
{
} }
void FfzEmotes::loadChannelEmotes(const QString &channelName, std::shared_ptr<const EmoteMap> FfzEmotes::global() const
std::function<void(EmoteMap &&)> callback) {
return this->global_.get();
}
boost::optional<EmotePtr> FfzEmotes::global(const EmoteName &name) const
{
auto emotes = this->global_.get();
auto it = emotes->find(name);
if (it != emotes->end()) return it->second;
return boost::none;
}
void FfzEmotes::loadGlobal()
{
QString url("https://api.frankerfacez.com/v1/set/global");
NetworkRequest request(url);
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
request.onSuccess([this](auto result) -> Outcome {
auto emotes = this->global();
auto pair = parseGlobalEmotes(result.parseJson(), *emotes);
if (pair.first)
this->global_.set(
std::make_shared<EmoteMap>(std::move(pair.second)));
return pair.first;
});
request.execute();
}
void FfzEmotes::loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{ {
// printf("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n", // printf("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n",
// qPrintable(channelName)); // qPrintable(channelName));

View file

@ -2,7 +2,7 @@
#include <memory> #include <memory>
#include "common/UniqueAccess.hpp" #include "common/Atomic.hpp"
#include "messages/Emote.hpp" #include "messages/Emote.hpp"
#include "messages/EmoteCache.hpp" #include "messages/EmoteCache.hpp"
@ -16,24 +16,17 @@ class FfzEmotes final : std::enable_shared_from_this<FfzEmotes>
"https://api.betterttv.net/2/channels/"; "https://api.betterttv.net/2/channels/";
public: public:
// FfzEmotes(); FfzEmotes();
static std::shared_ptr<FfzEmotes> create(); std::shared_ptr<const EmoteMap> global() const;
boost::optional<EmotePtr> global(const EmoteName &name) const;
AccessGuard<const EmoteCache<EmoteName>> accessGlobalEmotes() const; void loadGlobal();
boost::optional<EmotePtr> getGlobalEmote(const EmoteName &name); void loadChannel(const QString &channelName,
boost::optional<EmotePtr> getEmote(const EmoteId &id); std::function<void(EmoteMap &&)> callback);
void loadGlobalEmotes(); private:
void loadChannelEmotes(const QString &channelName, Atomic<std::shared_ptr<const EmoteMap>> global_;
std::function<void(EmoteMap &&)> callback);
protected:
Outcome parseGlobalEmotes(const QJsonObject &jsonRoot);
Outcome parseChannelEmotes(const QJsonObject &jsonRoot);
UniqueAccess<EmoteCache<EmoteName>> globalEmotes_;
UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -131,7 +131,7 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
chan->destroyed.connect([this, clojuresInCppAreShit] { chan->destroyed.connect([this, clojuresInCppAreShit] {
// fourtf: issues when the server itself is destroyed // fourtf: issues when the server itself is destroyed
Log("[AbstractIrcServer::addChannel] {} was destroyed", log("[AbstractIrcServer::addChannel] {} was destroyed",
clojuresInCppAreShit); clojuresInCppAreShit);
this->channels.remove(clojuresInCppAreShit); this->channels.remove(clojuresInCppAreShit);

View file

@ -145,7 +145,7 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
auto chan = app->twitch.server->getChannelOrEmpty(chanName); auto chan = app->twitch.server->getChannelOrEmpty(chanName);
if (chan->isEmpty()) { if (chan->isEmpty()) {
Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not " log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
"found", "found",
chanName); chanName);
return; return;
@ -209,7 +209,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message) void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
{ {
auto app = getApp(); auto app = getApp();
Log("Received whisper!"); log("Received whisper!");
MessageParseArgs args; MessageParseArgs args;
args.isReceivedWhisper = true; args.isReceivedWhisper = true;
@ -326,7 +326,7 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
auto channel = app->twitch.server->getChannelOrEmpty(channelName); auto channel = app->twitch.server->getChannelOrEmpty(channelName);
if (channel->isEmpty()) { if (channel->isEmpty()) {
Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel " log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
"manager ", "manager ",
channelName); channelName);
return; return;
@ -366,7 +366,7 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(
return; return;
} }
Log("Showing notice message from write connection with message id '{}'", log("Showing notice message from write connection with message id '{}'",
msgID); msgID);
} }

View file

@ -42,23 +42,23 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
request.onSuccess([successCallback](auto result) -> Outcome { request.onSuccess([successCallback](auto result) -> Outcome {
auto root = result.parseJson(); auto root = result.parseJson();
if (!root.value("users").isArray()) { if (!root.value("users").isArray()) {
Log("API Error while getting user id, users is not an array"); log("API Error while getting user id, users is not an array");
return Failure; return Failure;
} }
auto users = root.value("users").toArray(); auto users = root.value("users").toArray();
if (users.size() != 1) { if (users.size() != 1) {
Log("API Error while getting user id, users array size is not 1"); log("API Error while getting user id, users array size is not 1");
return Failure; return Failure;
} }
if (!users[0].isObject()) { if (!users[0].isObject()) {
Log("API Error while getting user id, first user is not an object"); log("API Error while getting user id, first user is not an object");
return Failure; return Failure;
} }
auto firstUser = users[0].toObject(); auto firstUser = users[0].toObject();
auto id = firstUser.value("_id"); auto id = firstUser.value("_id");
if (!id.isString()) { if (!id.isString()) {
Log("API Error: while getting user id, first user object `_id` key " log("API Error: while getting user id, first user object `_id` key "
"is not a " "is not a "
"string"); "string");
return Failure; return Failure;

View file

@ -109,7 +109,7 @@ void PubSubClient::handlePong()
{ {
assert(this->awaitingPong_); assert(this->awaitingPong_);
Log("Got pong!"); log("Got pong!");
this->awaitingPong_ = false; this->awaitingPong_ = false;
} }
@ -144,7 +144,7 @@ void PubSubClient::ping()
} }
if (self->awaitingPong_) { if (self->awaitingPong_) {
Log("No pong respnose, disconnect!"); log("No pong respnose, disconnect!");
// TODO(pajlada): Label this connection as "disconnect me" // TODO(pajlada): Label this connection as "disconnect me"
} }
}); });
@ -166,7 +166,7 @@ bool PubSubClient::send(const char *payload)
websocketpp::frame::opcode::text, ec); websocketpp::frame::opcode::text, ec);
if (ec) { if (ec) {
Log("Error sending message {}: {}", payload, ec.message()); log("Error sending message {}: {}", payload, ec.message());
// TODO(pajlada): Check which error code happened and maybe gracefully // TODO(pajlada): Check which error code happened and maybe gracefully
// handle it // handle it
@ -207,26 +207,26 @@ PubSub::PubSub()
action.state = ModeChangedAction::State::On; action.state = ModeChangedAction::State::On;
if (!data.HasMember("args")) { if (!data.HasMember("args")) {
Log("Missing required args member"); log("Missing required args member");
return; return;
} }
const auto &args = data["args"]; const auto &args = data["args"];
if (!args.IsArray()) { if (!args.IsArray()) {
Log("args member must be an array"); log("args member must be an array");
return; return;
} }
if (args.Size() == 0) { if (args.Size() == 0) {
Log("Missing duration argument in slowmode on"); log("Missing duration argument in slowmode on");
return; return;
} }
const auto &durationArg = args[0]; const auto &durationArg = args[0];
if (!durationArg.IsString()) { if (!durationArg.IsString()) {
Log("Duration arg must be a string"); log("Duration arg must be a string");
return; return;
} }
@ -314,7 +314,7 @@ PubSub::PubSub()
return; return;
} }
} catch (const std::runtime_error &ex) { } catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what()); log("Error parsing moderation action: {}", ex.what());
} }
action.modded = false; action.modded = false;
@ -339,7 +339,7 @@ PubSub::PubSub()
return; return;
} }
} catch (const std::runtime_error &ex) { } catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what()); log("Error parsing moderation action: {}", ex.what());
} }
action.modded = true; action.modded = true;
@ -380,7 +380,7 @@ PubSub::PubSub()
this->signals_.moderation.userBanned.invoke(action); this->signals_.moderation.userBanned.invoke(action);
} catch (const std::runtime_error &ex) { } catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what()); log("Error parsing moderation action: {}", ex.what());
} }
}; };
@ -410,7 +410,7 @@ PubSub::PubSub()
this->signals_.moderation.userBanned.invoke(action); this->signals_.moderation.userBanned.invoke(action);
} catch (const std::runtime_error &ex) { } catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what()); log("Error parsing moderation action: {}", ex.what());
} }
}; };
@ -436,7 +436,7 @@ PubSub::PubSub()
this->signals_.moderation.userUnbanned.invoke(action); this->signals_.moderation.userUnbanned.invoke(action);
} catch (const std::runtime_error &ex) { } catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what()); log("Error parsing moderation action: {}", ex.what());
} }
}; };
@ -462,7 +462,7 @@ PubSub::PubSub()
this->signals_.moderation.userUnbanned.invoke(action); this->signals_.moderation.userUnbanned.invoke(action);
} catch (const std::runtime_error &ex) { } catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what()); log("Error parsing moderation action: {}", ex.what());
} }
}; };
@ -493,7 +493,7 @@ void PubSub::addClient()
auto con = this->websocketClient.get_connection(TWITCH_PUBSUB_URL, ec); auto con = this->websocketClient.get_connection(TWITCH_PUBSUB_URL, ec);
if (ec) { if (ec) {
Log("Unable to establish connection: {}", ec.message()); log("Unable to establish connection: {}", ec.message());
return; return;
} }
@ -512,7 +512,7 @@ void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
std::string userID = account->getUserId().toStdString(); std::string userID = account->getUserId().toStdString();
Log("Connection open!"); log("Connection open!");
websocketpp::lib::error_code ec; websocketpp::lib::error_code ec;
std::vector<std::string> topics({"whispers." + userID}); std::vector<std::string> topics({"whispers." + userID});
@ -520,7 +520,7 @@ void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
this->listen(createListenMessage(topics, account)); this->listen(createListenMessage(topics, account));
if (ec) { if (ec) {
Log("Unable to send message to websocket server: {}", ec.message()); log("Unable to send message to websocket server: {}", ec.message());
return; return;
} }
} }
@ -544,11 +544,11 @@ void PubSub::listenToChannelModerationActions(
std::string topic(fS("chat_moderator_actions.{}.{}", userID, channelID)); std::string topic(fS("chat_moderator_actions.{}.{}", userID, channelID));
if (this->isListeningToTopic(topic)) { if (this->isListeningToTopic(topic)) {
Log("We are already listening to topic {}", topic); log("We are already listening to topic {}", topic);
return; return;
} }
Log("Listen to topic {}", topic); log("Listen to topic {}", topic);
this->listenToTopic(topic, account); this->listenToTopic(topic, account);
} }
@ -564,18 +564,18 @@ void PubSub::listenToTopic(const std::string &topic,
void PubSub::listen(rapidjson::Document &&msg) void PubSub::listen(rapidjson::Document &&msg)
{ {
if (this->tryListen(msg)) { if (this->tryListen(msg)) {
Log("Successfully listened!"); log("Successfully listened!");
return; return;
} }
Log("Added to the back of the queue"); log("Added to the back of the queue");
this->requests.emplace_back( this->requests.emplace_back(
std::make_unique<rapidjson::Document>(std::move(msg))); std::make_unique<rapidjson::Document>(std::move(msg)));
} }
bool PubSub::tryListen(rapidjson::Document &msg) bool PubSub::tryListen(rapidjson::Document &msg)
{ {
Log("tryListen with {} clients", this->clients.size()); log("tryListen with {} clients", this->clients.size());
for (const auto &p : this->clients) { for (const auto &p : this->clients) {
const auto &client = p.second; const auto &client = p.second;
if (client->listen(msg)) { if (client->listen(msg)) {
@ -608,13 +608,13 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
rapidjson::ParseResult res = msg.Parse(payload.c_str()); rapidjson::ParseResult res = msg.Parse(payload.c_str());
if (!res) { if (!res) {
Log("Error parsing message '{}' from PubSub: {}", payload, log("Error parsing message '{}' from PubSub: {}", payload,
rapidjson::GetParseError_En(res.Code())); rapidjson::GetParseError_En(res.Code()));
return; return;
} }
if (!msg.IsObject()) { if (!msg.IsObject()) {
Log("Error parsing message '{}' from PubSub. Root object is not an " log("Error parsing message '{}' from PubSub. Root object is not an "
"object", "object",
payload); payload);
return; return;
@ -623,7 +623,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
std::string type; std::string type;
if (!rj::getSafe(msg, "type", type)) { if (!rj::getSafe(msg, "type", type)) {
Log("Missing required string member `type` in message root"); log("Missing required string member `type` in message root");
return; return;
} }
@ -631,14 +631,14 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
this->handleListenResponse(msg); this->handleListenResponse(msg);
} else if (type == "MESSAGE") { } else if (type == "MESSAGE") {
if (!msg.HasMember("data")) { if (!msg.HasMember("data")) {
Log("Missing required object member `data` in message root"); log("Missing required object member `data` in message root");
return; return;
} }
const auto &data = msg["data"]; const auto &data = msg["data"];
if (!data.IsObject()) { if (!data.IsObject()) {
Log("Member `data` must be an object"); log("Member `data` must be an object");
return; return;
} }
@ -654,7 +654,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
client.second->handlePong(); client.second->handlePong();
} else { } else {
Log("Unknown message type: {}", type); log("Unknown message type: {}", type);
} }
} }
@ -699,7 +699,7 @@ PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::single_dh_use); boost::asio::ssl::context::single_dh_use);
} catch (const std::exception &e) { } catch (const std::exception &e) {
Log("Exception caught in OnTLSInit: {}", e.what()); log("Exception caught in OnTLSInit: {}", e.what());
} }
return ctx; return ctx;
@ -714,12 +714,12 @@ void PubSub::handleListenResponse(const rapidjson::Document &msg)
rj::getSafe(msg, "nonce", nonce); rj::getSafe(msg, "nonce", nonce);
if (error.empty()) { if (error.empty()) {
Log("Successfully listened to nonce {}", nonce); log("Successfully listened to nonce {}", nonce);
// Nothing went wrong // Nothing went wrong
return; return;
} }
Log("PubSub error: {} on nonce {}", error, nonce); log("PubSub error: {} on nonce {}", error, nonce);
return; return;
} }
} }
@ -729,14 +729,14 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
QString topic; QString topic;
if (!rj::getSafe(outerData, "topic", topic)) { if (!rj::getSafe(outerData, "topic", topic)) {
Log("Missing required string member `topic` in outerData"); log("Missing required string member `topic` in outerData");
return; return;
} }
std::string payload; std::string payload;
if (!rj::getSafe(outerData, "message", payload)) { if (!rj::getSafe(outerData, "message", payload)) {
Log("Expected string message in outerData"); log("Expected string message in outerData");
return; return;
} }
@ -745,7 +745,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
rapidjson::ParseResult res = msg.Parse(payload.c_str()); rapidjson::ParseResult res = msg.Parse(payload.c_str());
if (!res) { if (!res) {
Log("Error parsing message '{}' from PubSub: {}", payload, log("Error parsing message '{}' from PubSub: {}", payload,
rapidjson::GetParseError_En(res.Code())); rapidjson::GetParseError_En(res.Code()));
return; return;
} }
@ -754,7 +754,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
std::string whisperType; std::string whisperType;
if (!rj::getSafe(msg, "type", whisperType)) { if (!rj::getSafe(msg, "type", whisperType)) {
Log("Bad whisper data"); log("Bad whisper data");
return; return;
} }
@ -765,7 +765,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
} else if (whisperType == "thread") { } else if (whisperType == "thread") {
// Handle thread? // Handle thread?
} else { } else {
Log("Invalid whisper type: {}", whisperType); log("Invalid whisper type: {}", whisperType);
assert(false); assert(false);
return; return;
} }
@ -777,30 +777,30 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
std::string moderationAction; std::string moderationAction;
if (!rj::getSafe(data, "moderation_action", moderationAction)) { if (!rj::getSafe(data, "moderation_action", moderationAction)) {
Log("Missing moderation action in data: {}", rj::stringify(data)); log("Missing moderation action in data: {}", rj::stringify(data));
return; return;
} }
auto handlerIt = this->moderationActionHandlers.find(moderationAction); auto handlerIt = this->moderationActionHandlers.find(moderationAction);
if (handlerIt == this->moderationActionHandlers.end()) { if (handlerIt == this->moderationActionHandlers.end()) {
Log("No handler found for moderation action {}", moderationAction); log("No handler found for moderation action {}", moderationAction);
return; return;
} }
// Invoke handler function // Invoke handler function
handlerIt->second(data, topicParts[2]); handlerIt->second(data, topicParts[2]);
} else { } else {
Log("Unknown topic: {}", topic); log("Unknown topic: {}", topic);
return; return;
} }
} }
void PubSub::runThread() void PubSub::runThread()
{ {
Log("Start pubsub manager thread"); log("Start pubsub manager thread");
this->websocketClient.run(); this->websocketClient.run();
Log("Done with pubsub manager thread"); log("Done with pubsub manager thread");
} }
} // namespace chatterino } // namespace chatterino

View file

@ -35,7 +35,7 @@ void runAfter(boost::asio::io_service &ioService, Duration duration,
timer->async_wait([timer, cb](const boost::system::error_code &ec) { timer->async_wait([timer, cb](const boost::system::error_code &ec) {
if (ec) { if (ec) {
Log("Error in runAfter: {}", ec.message()); log("Error in runAfter: {}", ec.message());
return; return;
} }
@ -52,7 +52,7 @@ void runAfter(std::shared_ptr<boost::asio::steady_timer> timer,
timer->async_wait([timer, cb](const boost::system::error_code &ec) { timer->async_wait([timer, cb](const boost::system::error_code &ec) {
if (ec) { if (ec) {
Log("Error in runAfter: {}", ec.message()); log("Error in runAfter: {}", ec.message());
return; return;
} }

View file

@ -143,7 +143,7 @@ void TwitchAccount::loadIgnores()
} }
TwitchUser ignoredUser; TwitchUser ignoredUser;
if (!rj::getSafe(userIt->value, ignoredUser)) { if (!rj::getSafe(userIt->value, ignoredUser)) {
Log("Error parsing twitch user JSON {}", log("Error parsing twitch user JSON {}",
rj::stringify(userIt->value)); rj::stringify(userIt->value));
continue; continue;
} }
@ -368,13 +368,13 @@ std::set<TwitchUser> TwitchAccount::getIgnores() const
void TwitchAccount::loadEmotes() void TwitchAccount::loadEmotes()
{ {
Log("Loading Twitch emotes for user {}", this->getUserName()); log("Loading Twitch emotes for user {}", this->getUserName());
const auto &clientID = this->getOAuthClient(); const auto &clientID = this->getOAuthClient();
const auto &oauthToken = this->getOAuthToken(); const auto &oauthToken = this->getOAuthToken();
if (clientID.isEmpty() || oauthToken.isEmpty()) { if (clientID.isEmpty() || oauthToken.isEmpty()) {
Log("Missing Client ID or OAuth token"); log("Missing Client ID or OAuth token");
return; return;
} }
@ -386,7 +386,7 @@ void TwitchAccount::loadEmotes()
req.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken()); req.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken());
req.onError([=](int errorCode) { req.onError([=](int errorCode) {
Log("[TwitchAccount::loadEmotes] Error {}", errorCode); log("[TwitchAccount::loadEmotes] Error {}", errorCode);
if (errorCode == 203) { if (errorCode == 203) {
// onFinished(FollowResult_NotFollowing); // onFinished(FollowResult_NotFollowing);
} else { } else {
@ -420,7 +420,7 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
auto emoticonSets = root.FindMember("emoticon_sets"); auto emoticonSets = root.FindMember("emoticon_sets");
if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject()) { if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject()) {
Log("No emoticon_sets in load emotes response"); log("No emoticon_sets in load emotes response");
return; return;
} }
@ -434,19 +434,19 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
for (const rapidjson::Value &emoteJSON : for (const rapidjson::Value &emoteJSON :
emoteSetJSON.value.GetArray()) { emoteSetJSON.value.GetArray()) {
if (!emoteJSON.IsObject()) { if (!emoteJSON.IsObject()) {
Log("Emote value was invalid"); log("Emote value was invalid");
return; return;
} }
uint64_t idNumber; uint64_t idNumber;
if (!rj::getSafe(emoteJSON, "id", idNumber)) { if (!rj::getSafe(emoteJSON, "id", idNumber)) {
Log("No ID key found in Emote value"); log("No ID key found in Emote value");
return; return;
} }
QString _code; QString _code;
if (!rj::getSafe(emoteJSON, "code", _code)) { if (!rj::getSafe(emoteJSON, "code", _code)) {
Log("No code key found in Emote value"); log("No code key found in Emote value");
return; return;
} }
@ -468,7 +468,7 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet) void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
{ {
if (!emoteSet) { if (!emoteSet) {
Log("null emote set sent"); log("null emote set sent");
return; return;
} }
@ -486,7 +486,7 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
req.setUseQuickLoadCache(true); req.setUseQuickLoadCache(true);
req.onError([](int errorCode) -> bool { req.onError([](int errorCode) -> bool {
Log("Error code {} while loading emote set data", errorCode); log("Error code {} while loading emote set data", errorCode);
return true; return true;
}); });
@ -507,16 +507,15 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
return Failure; return Failure;
} }
Log("Loaded twitch emote set data for {}!", emoteSet->key); log("Loaded twitch emote set data for {}!", emoteSet->key);
if (type == "sub") { auto name = channelName;
emoteSet->text = name.detach();
QString("Twitch Subscriber Emote (%1)").arg(channelName); name[0] = name[0].toUpper();
} else {
emoteSet->text =
QString("Twitch Account Emote (%1)").arg(channelName);
}
emoteSet->text = name;
emoteSet->type = type;
emoteSet->channelName = channelName; emoteSet->channelName = channelName;
return Success; return Success;

View file

@ -44,6 +44,7 @@ public:
QString key; QString key;
QString channelName; QString channelName;
QString text; QString text;
QString type;
std::vector<TwitchEmote> emotes; std::vector<TwitchEmote> emotes;
}; };

View file

@ -93,20 +93,20 @@ void TwitchAccountManager::reloadUsers()
switch (this->addUser(userData)) { switch (this->addUser(userData)) {
case AddUserResponse::UserAlreadyExists: { case AddUserResponse::UserAlreadyExists: {
Log("User {} already exists", userData.username); log("User {} already exists", userData.username);
// Do nothing // Do nothing
} break; } break;
case AddUserResponse::UserValuesUpdated: { case AddUserResponse::UserValuesUpdated: {
Log("User {} already exists, and values updated!", log("User {} already exists, and values updated!",
userData.username); userData.username);
if (userData.username == this->getCurrent()->getUserName()) { if (userData.username == this->getCurrent()->getUserName()) {
Log("It was the current user, so we need to reconnect " log("It was the current user, so we need to reconnect "
"stuff!"); "stuff!");
this->currentUserChanged.invoke(); this->currentUserChanged.invoke();
} }
} break; } break;
case AddUserResponse::UserAdded: { case AddUserResponse::UserAdded: {
Log("Added user {}", userData.username); log("Added user {}", userData.username);
listUpdated = true; listUpdated = true;
} break; } break;
} }
@ -125,12 +125,12 @@ void TwitchAccountManager::load()
QString newUsername(QString::fromStdString(newValue)); QString newUsername(QString::fromStdString(newValue));
auto user = this->findUserByUsername(newUsername); auto user = this->findUserByUsername(newUsername);
if (user) { if (user) {
Log("[AccountManager:currentUsernameChanged] User successfully " log("[AccountManager:currentUsernameChanged] User successfully "
"updated to {}", "updated to {}",
newUsername); newUsername);
this->currentUser_ = user; this->currentUser_ = user;
} else { } else {
Log("[AccountManager:currentUsernameChanged] User successfully " log("[AccountManager:currentUsernameChanged] User successfully "
"updated to anonymous"); "updated to anonymous");
this->currentUser_ = this->anonymousUser_; this->currentUser_ = this->anonymousUser_;
} }

View file

@ -20,25 +20,25 @@ void TwitchApi::findUserId(const QString user,
request.onSuccess([successCallback](auto result) mutable -> Outcome { request.onSuccess([successCallback](auto result) mutable -> Outcome {
auto root = result.parseJson(); auto root = result.parseJson();
if (!root.value("users").isArray()) { if (!root.value("users").isArray()) {
Log("API Error while getting user id, users is not an array"); log("API Error while getting user id, users is not an array");
successCallback(""); successCallback("");
return Failure; return Failure;
} }
auto users = root.value("users").toArray(); auto users = root.value("users").toArray();
if (users.size() != 1) { if (users.size() != 1) {
Log("API Error while getting user id, users array size is not 1"); log("API Error while getting user id, users array size is not 1");
successCallback(""); successCallback("");
return Failure; return Failure;
} }
if (!users[0].isObject()) { if (!users[0].isObject()) {
Log("API Error while getting user id, first user is not an object"); log("API Error while getting user id, first user is not an object");
successCallback(""); successCallback("");
return Failure; return Failure;
} }
auto firstUser = users[0].toObject(); auto firstUser = users[0].toObject();
auto id = firstUser.value("_id"); auto id = firstUser.value("_id");
if (!id.isString()) { if (!id.isString()) {
Log("API Error: while getting user id, first user object `_id` key " log("API Error: while getting user id, first user object `_id` key "
"is not a " "is not a "
"string"); "string");
successCallback(""); successCallback("");

View file

@ -56,7 +56,7 @@ TwitchChannel::TwitchChannel(const QString &name)
, popoutPlayerUrl_("https://player.twitch.tv/?channel=" + name) , popoutPlayerUrl_("https://player.twitch.tv/?channel=" + name)
, mod_(false) , mod_(false)
{ {
Log("[TwitchChannel:{}] Opened", name); log("[TwitchChannel:{}] Opened", name);
// this->refreshChannelEmotes(); // this->refreshChannelEmotes();
// this->refreshViewerList(); // this->refreshViewerList();
@ -116,7 +116,7 @@ void TwitchChannel::refreshChannelEmotes()
if (auto shared = weak.lock()) // if (auto shared = weak.lock()) //
*this->bttvEmotes_.access() = emoteMap; *this->bttvEmotes_.access() = emoteMap;
}); });
getApp()->emotes->ffz.loadChannelEmotes( 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->ffzEmotes_.access() = emoteMap;
@ -136,7 +136,7 @@ void TwitchChannel::sendMessage(const QString &message)
return; return;
} }
Log("[TwitchChannel:{}] Send message: {}", this->getName(), message); log("[TwitchChannel:{}] Send message: {}", this->getName(), message);
// Do last message processing // Do last message processing
QString parsedMessage = app->emotes->emojis.replaceShortCodes(message); QString parsedMessage = app->emotes->emojis.replaceShortCodes(message);
@ -350,13 +350,13 @@ void TwitchChannel::refreshLiveStatus()
auto roomID = this->getRoomId(); auto roomID = this->getRoomId();
if (roomID.isEmpty()) { if (roomID.isEmpty()) {
Log("[TwitchChannel:{}] Refreshing live status (Missing ID)", log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
this->getName()); this->getName());
this->setLive(false); this->setLive(false);
return; return;
} }
Log("[TwitchChannel:{}] Refreshing live status", this->getName()); log("[TwitchChannel:{}] Refreshing live status", this->getName());
QString url("https://api.twitch.tv/kraken/streams/" + roomID); QString url("https://api.twitch.tv/kraken/streams/" + roomID);
@ -381,12 +381,12 @@ void TwitchChannel::refreshLiveStatus()
Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document) Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
{ {
if (!document.IsObject()) { if (!document.IsObject()) {
Log("[TwitchChannel:refreshLiveStatus] root is not an object"); log("[TwitchChannel:refreshLiveStatus] root is not an object");
return Failure; return Failure;
} }
if (!document.HasMember("stream")) { if (!document.HasMember("stream")) {
Log("[TwitchChannel:refreshLiveStatus] Missing stream in root"); log("[TwitchChannel:refreshLiveStatus] Missing stream in root");
return Failure; return Failure;
} }
@ -400,7 +400,7 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
if (!stream.HasMember("viewers") || !stream.HasMember("game") || if (!stream.HasMember("viewers") || !stream.HasMember("game") ||
!stream.HasMember("channel") || !stream.HasMember("created_at")) { !stream.HasMember("channel") || !stream.HasMember("created_at")) {
Log("[TwitchChannel:refreshLiveStatus] Missing members in stream"); log("[TwitchChannel:refreshLiveStatus] Missing members in stream");
this->setLive(false); this->setLive(false);
return Failure; return Failure;
} }
@ -408,7 +408,7 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
const rapidjson::Value &streamChannel = stream["channel"]; const rapidjson::Value &streamChannel = stream["channel"];
if (!streamChannel.IsObject() || !streamChannel.HasMember("status")) { if (!streamChannel.IsObject() || !streamChannel.HasMember("status")) {
Log("[TwitchChannel:refreshLiveStatus] Missing member \"status\" in " log("[TwitchChannel:refreshLiveStatus] Missing member \"status\" in "
"channel"); "channel");
return Failure; return Failure;
} }

View file

@ -4,7 +4,7 @@
#include "common/Channel.hpp" #include "common/Channel.hpp"
#include "common/Common.hpp" #include "common/Common.hpp"
#include "common/MutexValue.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"

View file

@ -6,7 +6,7 @@ namespace chatterino {
bool trimChannelName(const QString &channelName, QString &outChannelName) bool trimChannelName(const QString &channelName, QString &outChannelName)
{ {
if (channelName.length() < 3) { if (channelName.length() < 3) {
Log("channel name length below 3"); log("channel name length below 3");
return false; return false;
} }

View file

@ -55,7 +55,7 @@ bool TwitchMessageBuilder::isIgnored() const
// TODO(pajlada): Do we need to check if the phrase is valid first? // TODO(pajlada): Do we need to check if the phrase is valid first?
for (const auto &phrase : app->ignores->phrases.getVector()) { for (const auto &phrase : app->ignores->phrases.getVector()) {
if (phrase.isMatch(this->originalMessage_)) { if (phrase.isMatch(this->originalMessage_)) {
Log("Blocking message because it contains ignored phrase {}", log("Blocking message because it contains ignored phrase {}",
phrase.getPattern()); phrase.getPattern());
return true; return true;
} }
@ -68,7 +68,7 @@ bool TwitchMessageBuilder::isIgnored() const
for (const auto &user : for (const auto &user :
app->accounts->twitch.getCurrent()->getIgnores()) { app->accounts->twitch.getCurrent()->getIgnores()) {
if (sourceUserID == user.id) { if (sourceUserID == user.id) {
Log("Blocking message because it's from blocked user {}", log("Blocking message because it's from blocked user {}",
user.name); user.name);
return true; return true;
} }
@ -533,7 +533,7 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
if (!app->highlights->blacklistContains(this->ircMessage->nick())) { if (!app->highlights->blacklistContains(this->ircMessage->nick())) {
for (const HighlightPhrase &highlight : activeHighlights) { for (const HighlightPhrase &highlight : activeHighlights) {
if (highlight.isMatch(this->originalMessage_)) { if (highlight.isMatch(this->originalMessage_)) {
Log("Highlight because {} matches {}", this->originalMessage_, log("Highlight because {} matches {}", this->originalMessage_,
highlight.getPattern()); highlight.getPattern());
doHighlight = true; doHighlight = true;
@ -555,7 +555,7 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
} }
for (const HighlightPhrase &userHighlight : userHighlights) { for (const HighlightPhrase &userHighlight : userHighlights) {
if (userHighlight.isMatch(this->ircMessage->nick())) { if (userHighlight.isMatch(this->ircMessage->nick())) {
Log("Highlight because user {} sent a message", log("Highlight because user {} sent a message",
this->ircMessage->nick()); this->ircMessage->nick());
doHighlight = true; doHighlight = true;
@ -638,12 +638,12 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
auto flags = MessageElementFlags(); auto flags = MessageElementFlags();
auto emote = boost::optional<EmotePtr>{}; auto emote = boost::optional<EmotePtr>{};
if ((emote = getApp()->emotes->bttv.getGlobalEmote(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->getBttvEmote(name))) {
flags = MessageElementFlag::BttvEmote; flags = MessageElementFlag::BttvEmote;
} else if ((emote = getApp()->emotes->ffz.getGlobalEmote(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->getFfzEmote(name))) {

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "messages/MessageBuilder.hpp" #include "messages/MessageBuilder.hpp"
#include "messages/MessageParseArgs.hpp"
#include "singletons/Emotes.hpp" #include "singletons/Emotes.hpp"
#include <IrcMessage> #include <IrcMessage>

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "common/MutexValue.hpp" #include "common/Atomic.hpp"
#include "common/Singleton.hpp" #include "common/Singleton.hpp"
#include "providers/irc/AbstractIrcServer.hpp" #include "providers/irc/AbstractIrcServer.hpp"
#include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchAccount.hpp"
@ -29,7 +29,7 @@ public:
std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID); std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID);
MutexValue<QString> lastUserThatWhisperedMe; Atomic<QString> lastUserThatWhisperedMe;
const ChannelPtr whispersChannel; const ChannelPtr whispersChannel;
const ChannelPtr mentionsChannel; const ChannelPtr mentionsChannel;

View file

@ -15,8 +15,8 @@ void Emotes::initialize(Settings &settings, Paths &paths)
[] { getApp()->accounts->twitch.getCurrent()->loadEmotes(); }); [] { getApp()->accounts->twitch.getCurrent()->loadEmotes(); });
this->emojis.load(); this->emojis.load();
this->bttv.loadGlobalEmotes(); this->bttv.loadGlobal();
this->ffz.loadGlobalEmotes(); this->ffz.loadGlobal();
this->gifTimer.initialize(); this->gifTimer.initialize();
} }

View file

@ -50,7 +50,7 @@ void Settings::saveSnapshot()
this->snapshot_.reset(d); this->snapshot_.reset(d);
Log("hehe: {}", pajlada::Settings::SettingManager::stringify(*d)); log("hehe: {}", pajlada::Settings::SettingManager::stringify(*d));
} }
void Settings::restoreSnapshot() void Settings::restoreSnapshot()
@ -64,14 +64,14 @@ void Settings::restoreSnapshot()
for (const auto &weakSetting : _settings) { for (const auto &weakSetting : _settings) {
auto setting = weakSetting.lock(); auto setting = weakSetting.lock();
if (!setting) { if (!setting) {
Log("Error stage 1 of loading"); log("Error stage 1 of loading");
continue; continue;
} }
const char *path = setting->getPath().c_str(); const char *path = setting->getPath().c_str();
if (!snapshotObject.HasMember(path)) { if (!snapshotObject.HasMember(path)) {
Log("Error stage 2 of loading"); log("Error stage 2 of loading");
continue; continue;
} }

View file

@ -211,7 +211,7 @@ Window *WindowManager::windowAt(int index)
if (index < 0 || (size_t)index >= this->windows_.size()) { if (index < 0 || (size_t)index >= this->windows_.size()) {
return nullptr; return nullptr;
} }
Log("getting window at bad index {}", index); log("getting window at bad index {}", index);
return this->windows_.at(index); return this->windows_.at(index);
} }

View file

@ -65,13 +65,13 @@ void LoggingChannel::openLogFile()
this->baseDirectory + QDir::separator() + this->subDirectory; this->baseDirectory + QDir::separator() + this->subDirectory;
if (!QDir().mkpath(directory)) { if (!QDir().mkpath(directory)) {
Log("Unable to create logging path"); log("Unable to create logging path");
return; return;
} }
// Open file handle to log file of current date // Open file handle to log file of current date
QString fileName = directory + QDir::separator() + baseFileName; QString fileName = directory + QDir::separator() + baseFileName;
Log("Logging to {}", fileName); log("Logging to {}", fileName);
this->fileHandle.setFileName(fileName); this->fileHandle.setFileName(fileName);
this->fileHandle.open(QIODevice::Append); this->fileHandle.open(QIODevice::Append);

View file

@ -6,7 +6,7 @@
namespace chatterino { namespace chatterino {
template <typename... Args> template <typename... Args>
auto fS(Args &&... args) -> decltype(fmt::format(std::forward<Args>(args)...)) auto fS(Args &&... args)
{ {
return fmt::format(std::forward<Args>(args)...); return fmt::format(std::forward<Args>(args)...);
} }

View file

@ -2,6 +2,7 @@
#include "Application.hpp" #include "Application.hpp"
#include "controllers/accounts/AccountController.hpp" #include "controllers/accounts/AccountController.hpp"
#include "debug/Benchmark.hpp"
#include "messages/MessageBuilder.hpp" #include "messages/MessageBuilder.hpp"
#include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchChannel.hpp"
#include "widgets/Notebook.hpp" #include "widgets/Notebook.hpp"
@ -11,36 +12,88 @@
#include <QTabWidget> #include <QTabWidget>
namespace chatterino { namespace chatterino {
namespace {
auto makeTitleMessage(const QString &title)
{
MessageBuilder builder;
builder.emplace<TextElement>(title, MessageElementFlag::Text);
builder->flags.set(MessageFlag::Centered);
return builder.release();
}
auto makeEmoteMessage(const EmoteMap &map)
{
MessageBuilder builder;
builder->flags.set(MessageFlag::Centered);
builder->flags.set(MessageFlag::DisableCompactEmotes);
for (const auto &emote : map) {
builder
.emplace<EmoteElement>(emote.second, MessageElementFlag::AlwaysShow)
->setLink(Link(Link::InsertText, emote.first.string));
}
return builder.release();
}
void addEmoteSets(std::vector<std::shared_ptr<TwitchAccount::EmoteSet>> sets,
Channel &globalChannel, Channel &subChannel)
{
for (const auto &set : sets) {
auto &channel = set->key == "0" ? globalChannel : subChannel;
// TITLE
auto text =
set->key == "0" || set->text.isEmpty() ? "Twitch" : set->text;
channel.addMessage(makeTitleMessage(text));
// EMOTES
MessageBuilder builder;
builder->flags.set(MessageFlag::Centered);
builder->flags.set(MessageFlag::DisableCompactEmotes);
for (const auto &emote : set->emotes) {
builder
.emplace<EmoteElement>(
getApp()->emotes->twitch.getOrCreateEmote(emote.id,
emote.name),
MessageElementFlag::AlwaysShow)
->setLink(Link(Link::InsertText, emote.name.string));
}
channel.addMessage(builder.release());
}
}
} // namespace
EmotePopup::EmotePopup() EmotePopup::EmotePopup()
: BaseWindow(nullptr, BaseWindow::EnableCustomFrame) : BaseWindow(nullptr, BaseWindow::EnableCustomFrame)
{ {
this->viewEmotes_ = new ChannelView(); auto layout = new QVBoxLayout(this);
this->viewEmojis_ = new ChannelView();
this->viewEmotes_->setOverrideFlags(MessageElementFlags{
MessageElementFlag::Default, MessageElementFlag::AlwaysShow,
MessageElementFlag::EmoteImages});
this->viewEmojis_->setOverrideFlags(MessageElementFlags{
MessageElementFlag::Default, MessageElementFlag::AlwaysShow,
MessageElementFlag::EmoteImages});
this->viewEmotes_->setEnableScrollingToBottom(false);
this->viewEmojis_->setEnableScrollingToBottom(false);
auto *layout = new QVBoxLayout(this);
this->getLayoutContainer()->setLayout(layout); this->getLayoutContainer()->setLayout(layout);
Notebook *notebook = new Notebook(this); auto notebook = new Notebook(this);
layout->addWidget(notebook); layout->addWidget(notebook);
layout->setMargin(0); layout->setMargin(0);
notebook->addPage(this->viewEmotes_, "Emotes"); auto makeView = [&](QString tabTitle) {
notebook->addPage(this->viewEmojis_, "Emojis"); auto view = new ChannelView();
view->setOverrideFlags(MessageElementFlags{
MessageElementFlag::Default, MessageElementFlag::AlwaysShow,
MessageElementFlag::EmoteImages});
view->setEnableScrollingToBottom(false);
notebook->addPage(view, tabTitle);
return view;
};
this->subEmotesView_ = makeView("Subs");
this->channelEmotesView_ = makeView("Channel");
this->globalEmotesView_ = makeView("Global");
this->viewEmojis_ = makeView("Emojis");
this->loadEmojis(); this->loadEmojis();
this->viewEmotes_->linkClicked.connect( this->globalEmotesView_->linkClicked.connect(
[this](const Link &link) { this->linkClicked.invoke(link); }); [this](const Link &link) { this->linkClicked.invoke(link); });
this->viewEmojis_->linkClicked.connect( this->viewEmojis_->linkClicked.connect(
[this](const Link &link) { this->linkClicked.invoke(link); }); [this](const Link &link) { this->linkClicked.invoke(link); });
@ -48,93 +101,41 @@ EmotePopup::EmotePopup()
void EmotePopup::loadChannel(ChannelPtr _channel) void EmotePopup::loadChannel(ChannelPtr _channel)
{ {
BenchmarkGuard guard("loadChannel");
this->setWindowTitle("Emotes from " + _channel->getName()); this->setWindowTitle("Emotes from " + _channel->getName());
TwitchChannel *channel = dynamic_cast<TwitchChannel *>(_channel.get()); auto twitchChannel = dynamic_cast<TwitchChannel *>(_channel.get());
if (twitchChannel == nullptr) return;
if (channel == nullptr) { auto addEmotes = [&](Channel &channel, const EmoteMap &map,
return; const QString &title) {
} channel.addMessage(makeTitleMessage(title));
channel.addMessage(makeEmoteMessage(map));
ChannelPtr emoteChannel(new Channel("", Channel::Type::None));
auto addEmotes = [&](const EmoteMap &map, const QString &title,
const QString &emoteDesc) {
// TITLE
MessageBuilder builder1;
builder1.emplace<TextElement>(title, MessageElementFlag::Text);
builder1->flags.set(MessageFlag::Centered);
emoteChannel->addMessage(builder1.release());
// EMOTES
MessageBuilder builder2;
builder2->flags.set(MessageFlag::Centered);
builder2->flags.set(MessageFlag::DisableCompactEmotes);
for (auto emote : map) {
builder2
.emplace<EmoteElement>(emote.second,
MessageElementFlag::AlwaysShow)
->setLink(Link(Link::InsertText, emote.first.string));
}
emoteChannel->addMessage(builder2.release());
}; };
auto app = getApp(); auto subChannel = std::make_shared<Channel>("", Channel::Type::None);
auto globalChannel = std::make_shared<Channel>("", Channel::Type::None);
auto channelChannel = std::make_shared<Channel>("", Channel::Type::None);
// fourtf: the entire emote manager needs to be refactored so there's no // twitch
// point in trying to fix this pile of garbage addEmoteSets(
for (const auto &set : getApp()->accounts->twitch.getCurrent()->accessEmotes()->emoteSets,
app->accounts->twitch.getCurrent()->accessEmotes()->emoteSets) { *globalChannel, *subChannel);
// TITLE
MessageBuilder builder1;
QString setText; // global
if (set->text.isEmpty()) { addEmotes(*globalChannel, *getApp()->emotes->bttv.global(), "BetterTTV");
if (set->channelName.isEmpty()) { addEmotes(*globalChannel, *getApp()->emotes->ffz.global(), "FrankerFaceZ");
setText = "Twitch Account Emotes";
} else {
setText = "Twitch Account Emotes (" + set->channelName + ")";
}
} else {
setText = set->text;
}
builder1.emplace<TextElement>(setText, MessageElementFlag::Text); // channel
// addEmotes(*channel->accessBttvEmotes(), "BetterTTV Channel Emotes",
// "BetterTTV Channel Emote");
// addEmotes(*channel->accessFfzEmotes(), "FrankerFaceZ Channel Emotes",
// "FrankerFaceZ Channel Emote");
builder1->flags.set(MessageFlag::Centered); this->globalEmotesView_->setChannel(globalChannel);
emoteChannel->addMessage(builder1.release()); this->subEmotesView_->setChannel(subChannel);
this->channelEmotesView_->setChannel(channelChannel);
// EMOTES
MessageBuilder builder2;
builder2->flags.set(MessageFlag::Centered);
builder2->flags.set(MessageFlag::DisableCompactEmotes);
for (const auto &emote : set->emotes) {
builder2
.emplace<EmoteElement>(
app->emotes->twitch.getOrCreateEmote(emote.id, emote.name),
MessageElementFlag::AlwaysShow)
->setLink(Link(Link::InsertText, emote.name.string));
}
emoteChannel->addMessage(builder2.release());
}
addEmotes(*app->emotes->bttv.accessGlobalEmotes(),
"BetterTTV Global Emotes", "BetterTTV Global Emote");
addEmotes(*channel->accessBttvEmotes(), "BetterTTV Channel Emotes",
"BetterTTV Channel Emote");
// addEmotes(*app->emotes->ffz.accessGlobalEmotes(), "FrankerFaceZ Global
// Emotes",
// "FrankerFaceZ Global Emote");
addEmotes(*channel->accessFfzEmotes(), "FrankerFaceZ Channel Emotes",
"FrankerFaceZ Channel Emote");
this->viewEmotes_->setChannel(emoteChannel);
} }
void EmotePopup::loadEmojis() void EmotePopup::loadEmojis()
@ -143,13 +144,6 @@ void EmotePopup::loadEmojis()
ChannelPtr emojiChannel(new Channel("", Channel::Type::None)); ChannelPtr emojiChannel(new Channel("", Channel::Type::None));
// title
MessageBuilder builder1;
builder1.emplace<TextElement>("emojis", MessageElementFlag::Text);
builder1->flags.set(MessageFlag::Centered);
emojiChannel->addMessage(builder1.release());
// emojis // emojis
MessageBuilder builder; MessageBuilder builder;
builder->flags.set(MessageFlag::Centered); builder->flags.set(MessageFlag::Centered);

View file

@ -19,8 +19,10 @@ public:
pajlada::Signals::Signal<Link> linkClicked; pajlada::Signals::Signal<Link> linkClicked;
private: private:
ChannelView *viewEmotes_; ChannelView *globalEmotesView_{};
ChannelView *viewEmojis_; ChannelView *channelEmotesView_{};
ChannelView *subEmotesView_{};
ChannelView *viewEmojis_{};
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -47,7 +47,7 @@ void QualityPopup::okButtonClicked()
try { try {
openStreamlink(channelURL, this->ui_.selector.currentText()); openStreamlink(channelURL, this->ui_.selector.currentText());
} catch (const Exception &ex) { } catch (const Exception &ex) {
Log("Exception caught trying to open streamlink: {}", ex.what()); log("Exception caught trying to open streamlink: {}", ex.what());
} }
this->close(); this->close();

View file

@ -492,7 +492,7 @@ void ChannelView::setChannel(ChannelPtr newChannel)
MessageLayoutPtr newItem(new MessageLayout(replacement)); MessageLayoutPtr newItem(new MessageLayout(replacement));
auto snapshot = this->messages.getSnapshot(); auto snapshot = this->messages.getSnapshot();
if (index >= snapshot.getLength()) { if (index >= snapshot.getLength()) {
Log("Tried to replace out of bounds message. Index: {}. " log("Tried to replace out of bounds message. Index: {}. "
"Length: {}", "Length: {}",
index, snapshot.getLength()); index, snapshot.getLength());
return; return;
@ -1169,19 +1169,18 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
userPopup->show(); userPopup->show();
qDebug() << "Clicked " << user << "s message"; qDebug() << "Clicked " << user << "s message";
break;
} } break;
case Link::Url: { case Link::Url: {
QDesktopServices::openUrl(QUrl(link.value)); QDesktopServices::openUrl(QUrl(link.value));
break; } break;
}
case Link::UserAction: { case Link::UserAction: {
QString value = link.value; QString value = link.value;
value.replace("{user}", layout->getMessage()->loginName); value.replace("{user}", layout->getMessage()->loginName);
this->channel_->sendMessage(value); this->channel_->sendMessage(value);
} } break;
default:; default:;
} }

View file

@ -137,7 +137,7 @@ AboutPage::AboutPage()
QStringList contributorParts = line.split("|"); QStringList contributorParts = line.split("|");
if (contributorParts.size() != 4) { if (contributorParts.size() != 4) {
Log("Missing parts in line '{}'", line); log("Missing parts in line '{}'", line);
continue; continue;
} }

View file

@ -429,7 +429,7 @@ void Split::openInStreamlink()
try { try {
openStreamlinkForChannel(this->getChannel()->getName()); openStreamlinkForChannel(this->getChannel()->getName());
} catch (const Exception &ex) { } catch (const Exception &ex) {
Log("Error in doOpenStreamlink: {}", ex.what()); log("Error in doOpenStreamlink: {}", ex.what());
} }
} }