Merge branch 'master' into apa-notification-on-live

This commit is contained in:
apa420
2018-08-28 23:23:46 +02:00
committed by GitHub
214 changed files with 4368 additions and 4428 deletions
+11 -10
View File
@@ -7,11 +7,13 @@
#include "controllers/moderationactions/ModerationActions.hpp"
#include "controllers/notifications/NotificationController.hpp"
#include "controllers/taggedusers/TaggedUsersController.hpp"
#include "debug/Log.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/ffz/FfzEmotes.hpp"
#include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/Logging.hpp"
#include "singletons/NativeMessaging.hpp"
@@ -23,6 +25,7 @@
#include "singletons/WindowManager.hpp"
#include "util/IsBigEndian.hpp"
#include "util/PostToThread.hpp"
#include "widgets/Window.hpp"
#include <atomic>
@@ -37,9 +40,7 @@ Application *Application::instance = nullptr;
// to each other
Application::Application(Settings &_settings, Paths &_paths)
: settings(&_settings)
, paths(&_paths)
, resources(&this->emplace<Resources2>())
: resources(&this->emplace<Resources2>())
, themes(&this->emplace<Theme>())
, fonts(&this->emplace<Fonts>())
@@ -103,26 +104,26 @@ void Application::save()
void Application::initNm()
{
#ifdef Q_OS_WIN
#ifdef QT_DEBUG
#ifdef C_DEBUG_NM
# ifdef QT_DEBUG
# ifdef C_DEBUG_NM
this->nativeMessaging->registerHost();
this->nativeMessaging->openGuiMessageQueue();
#endif
#else
# endif
# else
this->nativeMessaging->registerHost();
this->nativeMessaging->openGuiMessageQueue();
#endif
# endif
#endif
}
void Application::initPubsub()
{
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) {
Log("WHISPER RECEIVED LOL"); //
log("WHISPER RECEIVED LOL"); //
});
this->twitch.pubsub->signals_.moderation.chatCleared.connect(
+2 -4
View File
@@ -1,7 +1,6 @@
#pragma once
#include "common/Singleton.hpp"
#include "singletons/Resources.hpp"
#include <QApplication>
#include <memory>
@@ -27,9 +26,10 @@ class AccountManager;
class Emotes;
class Settings;
class Fonts;
class Resources;
class Resources2;
class Toasts;
class Application
{
std::vector<std::unique_ptr<Singleton>> singletons_;
@@ -49,8 +49,6 @@ public:
friend void test();
Settings *const settings{};
Paths *const paths{};
Resources2 *const resources;
Theme *const themes{};
+21 -23
View File
@@ -9,33 +9,31 @@
#include <memory>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
# include <fcntl.h>
# include <io.h>
# include <stdio.h>
#endif
namespace chatterino {
namespace {
void initFileMode()
{
void initFileMode()
{
#ifdef Q_OS_WIN
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
#endif
}
}
void runLoop(NativeMessagingClient &client)
{
while (true) {
char size_c[4];
std::cin.read(size_c, 4);
void runLoop(NativeMessagingClient &client)
{
while (true) {
char size_c[4];
std::cin.read(size_c, 4);
if (std::cin.eof()) {
break;
}
if (std::cin.eof()) break;
uint32_t size = *reinterpret_cast<uint32_t *>(size_c);
auto size = *reinterpret_cast<uint32_t *>(size_c);
#if 0
bool bigEndian = isBigEndian();
@@ -50,14 +48,14 @@ void runLoop(NativeMessagingClient &client)
}
#endif
std::unique_ptr<char[]> b(new char[size + 1]);
std::cin.read(b.get(), size);
*(b.get() + size) = '\0';
std::unique_ptr<char[]> buffer(new char[size + 1]);
std::cin.read(buffer.get(), size);
*(buffer.get() + size) = '\0';
client.sendMessage(
QByteArray::fromRawData(b.get(), static_cast<int32_t>(size)));
client.sendMessage(QByteArray::fromRawData(
buffer.get(), static_cast<int32_t>(size)));
}
}
}
} // namespace
bool shouldRunBrowserExtensionHost(const QStringList &args)
+163 -163
View File
@@ -1,168 +1,168 @@
#ifdef __cplusplus
#include <fmt/format.h>
#include <irccommand.h>
#include <ircconnection.h>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/error/error.h>
#include <IrcMessage>
#include <QAbstractListModel>
#include <QAbstractNativeEventFilter>
#include <QAction>
#include <QApplication>
#include <QBrush>
#include <QBuffer>
#include <QButtonGroup>
#include <QByteArray>
#include <QCheckBox>
#include <QClipboard>
#include <QColor>
#include <QComboBox>
#include <QCompleter>
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDesktopServices>
#include <QDialog>
#include <QDialogButtonBox>
#include <QDir>
#include <QDockWidget>
#include <QDrag>
#include <QDragEnterEvent>
#include <QElapsedTimer>
#include <QEventLoop>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QFlags>
#include <QFont>
#include <QFontDatabase>
#include <QFontDialog>
#include <QFontMetrics>
#include <QFormLayout>
#include <QGraphicsBlurEffect>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QIcon>
#include <QImageReader>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QKeyEvent>
#include <QLabel>
#include <QLayout>
#include <QLibrary>
#include <QLineEdit>
#include <QList>
#include <QListView>
#include <QListWidget>
#include <QMap>
#include <QMediaPlayer>
#include <QMenu>
#include <QMessageBox>
#include <QMimeData>
#include <QMouseEvent>
#include <QMutex>
#include <QMutexLocker>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QObject>
#include <QPaintEvent>
#include <QPainter>
#include <QPainterPath>
#include <QPalette>
#include <QPixmap>
#include <QPoint>
#include <QProcess>
#include <QPropertyAnimation>
#include <QPushButton>
#include <QRadialGradient>
#include <QRect>
#include <QRegularExpression>
#include <QRunnable>
#include <QScroller>
#include <QShortcut>
#include <QSizePolicy>
#include <QSlider>
#include <QStackedLayout>
#include <QStandardPaths>
#include <QString>
#include <QStyle>
#include <QStyleOption>
#include <QTabWidget>
#include <QTextEdit>
#include <QThread>
#include <QThreadPool>
#include <QTime>
#include <QTimer>
#include <QUrl>
#include <QUuid>
#include <QVBoxLayout>
#include <QVariant>
#include <QVector>
#include <QWheelEvent>
#include <QWidget>
#include <QtCore/QVariant>
#include <QtGlobal>
#include <QtWidgets/QAction>
#include <QtWidgets/QApplication>
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QDialog>
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QFormLayout>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTabWidget>
#include <QtWidgets/QVBoxLayout>
#include <algorithm>
#include <boost/current_function.hpp>
#include <boost/foreach.hpp>
#include <boost/noncopyable.hpp>
#include <boost/optional.hpp>
#include <cassert>
#include <chrono>
#include <cinttypes>
#include <climits>
#include <cmath>
#include <cstdint>
#include <ctime>
#include <functional>
#include <future>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <pajlada/settings/serialize.hpp>
#include <pajlada/settings/setting.hpp>
#include <pajlada/settings/settinglistener.hpp>
#include <pajlada/signals/connection.hpp>
#include <pajlada/signals/signal.hpp>
#include <random>
#include <set>
#include <string>
#include <thread>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
# include <fmt/format.h>
# include <irccommand.h>
# include <ircconnection.h>
# include <rapidjson/document.h>
# include <rapidjson/error/en.h>
# include <rapidjson/error/error.h>
# include <IrcMessage>
# include <QAbstractListModel>
# include <QAbstractNativeEventFilter>
# include <QAction>
# include <QApplication>
# include <QBrush>
# include <QBuffer>
# include <QButtonGroup>
# include <QByteArray>
# include <QCheckBox>
# include <QClipboard>
# include <QColor>
# include <QComboBox>
# include <QCompleter>
# include <QCoreApplication>
# include <QDateTime>
# include <QDebug>
# include <QDesktopServices>
# include <QDialog>
# include <QDialogButtonBox>
# include <QDir>
# include <QDockWidget>
# include <QDrag>
# include <QDragEnterEvent>
# include <QElapsedTimer>
# include <QEventLoop>
# include <QFile>
# include <QFileDialog>
# include <QFileInfo>
# include <QFlags>
# include <QFont>
# include <QFontDatabase>
# include <QFontDialog>
# include <QFontMetrics>
# include <QFormLayout>
# include <QGraphicsBlurEffect>
# include <QGroupBox>
# include <QHBoxLayout>
# include <QHeaderView>
# include <QIcon>
# include <QImageReader>
# include <QJsonArray>
# include <QJsonDocument>
# include <QJsonObject>
# include <QJsonValue>
# include <QKeyEvent>
# include <QLabel>
# include <QLayout>
# include <QLibrary>
# include <QLineEdit>
# include <QList>
# include <QListView>
# include <QListWidget>
# include <QMap>
# include <QMediaPlayer>
# include <QMenu>
# include <QMessageBox>
# include <QMimeData>
# include <QMouseEvent>
# include <QMutex>
# include <QMutexLocker>
# include <QNetworkAccessManager>
# include <QNetworkReply>
# include <QNetworkRequest>
# include <QObject>
# include <QPaintEvent>
# include <QPainter>
# include <QPainterPath>
# include <QPalette>
# include <QPixmap>
# include <QPoint>
# include <QProcess>
# include <QPropertyAnimation>
# include <QPushButton>
# include <QRadialGradient>
# include <QRect>
# include <QRegularExpression>
# include <QRunnable>
# include <QScroller>
# include <QShortcut>
# include <QSizePolicy>
# include <QSlider>
# include <QStackedLayout>
# include <QStandardPaths>
# include <QString>
# include <QStyle>
# include <QStyleOption>
# include <QTabWidget>
# include <QTextEdit>
# include <QThread>
# include <QThreadPool>
# include <QTime>
# include <QTimer>
# include <QUrl>
# include <QUuid>
# include <QVBoxLayout>
# include <QVariant>
# include <QVector>
# include <QWheelEvent>
# include <QWidget>
# include <QtCore/QVariant>
# include <QtGlobal>
# include <QtWidgets/QAction>
# include <QtWidgets/QApplication>
# include <QtWidgets/QButtonGroup>
# include <QtWidgets/QDialog>
# include <QtWidgets/QDialogButtonBox>
# include <QtWidgets/QFormLayout>
# include <QtWidgets/QHBoxLayout>
# include <QtWidgets/QHeaderView>
# include <QtWidgets/QLabel>
# include <QtWidgets/QLineEdit>
# include <QtWidgets/QPushButton>
# include <QtWidgets/QTabWidget>
# include <QtWidgets/QVBoxLayout>
# include <algorithm>
# include <boost/current_function.hpp>
# include <boost/foreach.hpp>
# include <boost/noncopyable.hpp>
# include <boost/optional.hpp>
# include <cassert>
# include <chrono>
# include <cinttypes>
# include <climits>
# include <cmath>
# include <cstdint>
# include <ctime>
# include <functional>
# include <future>
# include <list>
# include <map>
# include <memory>
# include <mutex>
# include <pajlada/settings/serialize.hpp>
# include <pajlada/settings/setting.hpp>
# include <pajlada/settings/settinglistener.hpp>
# include <pajlada/signals/connection.hpp>
# include <pajlada/signals/signal.hpp>
# include <random>
# include <set>
# include <string>
# include <thread>
# include <tuple>
# include <type_traits>
# include <unordered_map>
# include <unordered_set>
# include <vector>
#ifndef UNUSED
#define UNUSED(x) (void)(x)
#endif
# ifndef UNUSED
# define UNUSED(x) (void)(x)
# endif
#ifndef ATTR_UNUSED
#ifdef Q_OS_WIN
#define ATTR_UNUSED
#else
#define ATTR_UNUSED __attribute__((unused))
#endif
#endif
# ifndef ATTR_UNUSED
# ifdef Q_OS_WIN
# define ATTR_UNUSED
# else
# define ATTR_UNUSED __attribute__((unused))
# endif
# endif
#endif
+71 -71
View File
@@ -12,7 +12,7 @@
#include "widgets/dialogs/LastRunCrashDialog.hpp"
#ifdef C_USE_BREAKPAD
#include <QBreakpadHandler.h>
# include <QBreakpadHandler.h>
#endif
// void initQt();
@@ -23,82 +23,82 @@
namespace chatterino {
namespace {
void installCustomPalette()
{
// borrowed from
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
QPalette darkPalette = qApp->palette();
void installCustomPalette()
{
// borrowed from
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
auto dark = qApp->palette();
darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText,
QColor(127, 127, 127));
darkPalette.setColor(QPalette::Base, QColor("#333"));
darkPalette.setColor(QPalette::AlternateBase, QColor("#444"));
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::Text,
QColor(127, 127, 127));
darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35));
darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText,
QColor(127, 127, 127));
darkPalette.setColor(QPalette::BrightText, Qt::red);
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Disabled, QPalette::Highlight,
QColor(80, 80, 80));
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText,
QColor(127, 127, 127));
dark.setColor(QPalette::Window, QColor(22, 22, 22));
dark.setColor(QPalette::WindowText, Qt::white);
dark.setColor(QPalette::Text, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::WindowText,
QColor(127, 127, 127));
dark.setColor(QPalette::Base, QColor("#333"));
dark.setColor(QPalette::AlternateBase, QColor("#444"));
dark.setColor(QPalette::ToolTipBase, Qt::white);
dark.setColor(QPalette::ToolTipText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::Text,
QColor(127, 127, 127));
dark.setColor(QPalette::Dark, QColor(35, 35, 35));
dark.setColor(QPalette::Shadow, QColor(20, 20, 20));
dark.setColor(QPalette::Button, QColor(70, 70, 70));
dark.setColor(QPalette::ButtonText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::ButtonText,
QColor(127, 127, 127));
dark.setColor(QPalette::BrightText, Qt::red);
dark.setColor(QPalette::Link, QColor(42, 130, 218));
dark.setColor(QPalette::Highlight, QColor(42, 130, 218));
dark.setColor(QPalette::Disabled, QPalette::Highlight,
QColor(80, 80, 80));
dark.setColor(QPalette::HighlightedText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::HighlightedText,
QColor(127, 127, 127));
qApp->setPalette(darkPalette);
}
void initQt()
{
// set up the QApplication flags
QApplication::setAttribute(Qt::AA_Use96Dpi, true);
#ifdef Q_OS_WIN32
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
#endif
QApplication::setStyle(QStyleFactory::create("Fusion"));
installCustomPalette();
}
void showLastCrashDialog()
{
#ifndef C_DISABLE_CRASH_DIALOG
LastRunCrashDialog dialog;
switch (dialog.exec()) {
case QDialog::Accepted: {
}; break;
default: {
_exit(0);
}
qApp->setPalette(dark);
}
void initQt()
{
// set up the QApplication flags
QApplication::setAttribute(Qt::AA_Use96Dpi, true);
#ifdef Q_OS_WIN32
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
#endif
}
void createRunningFile(const QString &path)
{
QFile runningFile(path);
QApplication::setStyle(QStyleFactory::create("Fusion"));
runningFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
runningFile.flush();
runningFile.close();
}
installCustomPalette();
}
void removeRunningFile(const QString &path)
{
QFile::remove(path);
}
void showLastCrashDialog()
{
#ifndef C_DISABLE_CRASH_DIALOG
LastRunCrashDialog dialog;
switch (dialog.exec()) {
case QDialog::Accepted: {
}; break;
default: {
_exit(0);
}
}
#endif
}
void createRunningFile(const QString &path)
{
QFile runningFile(path);
runningFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
runningFile.flush();
runningFile.close();
}
void removeRunningFile(const QString &path)
{
QFile::remove(path);
}
} // namespace
void runGui(QApplication &a, Paths &paths, Settings &settings)
@@ -109,7 +109,7 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
chatterino::Updates::getInstance().checkForUpdates();
#ifdef C_USE_BREAKPAD
QBreakpadInstance.setDumpPath(app->paths->settingsFolderPath + "/Crashes");
QBreakpadInstance.setDumpPath(getPaths()->settingsFolderPath + "/Crashes");
#endif
// Running file
+2
View File
@@ -32,3 +32,5 @@ QStringAlias(UserName);
QStringAlias(UserId);
QStringAlias(Url);
QStringAlias(Tooltip);
QStringAlias(EmoteId);
QStringAlias(EmoteName);
@@ -6,14 +6,14 @@
namespace chatterino {
template <typename T>
class MutexValue : boost::noncopyable
class Atomic : boost::noncopyable
{
public:
MutexValue()
Atomic()
{
}
MutexValue(T &&val)
Atomic(T &&val)
: value_(val)
{
}
@@ -32,6 +32,13 @@ public:
this->value_ = val;
}
void set(T &&val)
{
std::lock_guard<std::mutex> guard(this->mutex_);
this->value_ = std::move(val);
}
private:
mutable std::mutex mutex_;
T value_;
+47 -16
View File
@@ -18,16 +18,14 @@
namespace chatterino {
//
// Channel
//
Channel::Channel(const QString &name, Type type)
: completionModel(name)
: completionModel(*this)
, name_(name)
, type_(type)
{
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout,
[this]() {
this->completionModel.clearExpiredStrings(); //
});
this->clearCompletionModelTimer_.start(60 * 1000);
}
Channel::~Channel()
@@ -67,8 +65,7 @@ void Channel::addMessage(MessagePtr message)
const QString &username = message->loginName;
if (!username.isEmpty()) {
// TODO: Add recent chatters display name. This should maybe be a
// setting
// TODO: Add recent chatters display name
this->addRecentChatter(message);
}
@@ -98,8 +95,6 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
for (int i = snapshotLength - 1; i >= end; --i) {
auto &s = snapshot[i];
qDebug() << s->parseTime << minimumTime;
if (s->parseTime < minimumTime) {
break;
}
@@ -165,13 +160,14 @@ void Channel::disableAllMessages()
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
int snapshotLength = snapshot.getLength();
for (int i = 0; i < snapshotLength; i++) {
auto &s = snapshot[i];
if (s->flags.hasAny({MessageFlag::System, MessageFlag::Timeout})) {
auto &message = snapshot[i];
if (message->flags.hasAny(
{MessageFlag::System, MessageFlag::Timeout})) {
continue;
}
// FOURTF: disabled for now
// s->flags.EnableFlag(MessageFlag::Disabled);
const_cast<Message *>(message.get())->flags.set(MessageFlag::Disabled);
}
}
@@ -196,7 +192,6 @@ void Channel::replaceMessage(MessagePtr message, MessagePtr replacement)
void Channel::addRecentChatter(const MessagePtr &message)
{
// Do nothing by default
}
bool Channel::canSendMessage() const
@@ -234,9 +229,45 @@ void Channel::onConnected()
{
}
std::weak_ptr<Channel> Channel::weak_from_this()
//
// Indirect channel
//
IndirectChannel::Data::Data(ChannelPtr _channel, Channel::Type _type)
: channel(_channel)
, type(_type)
{
return std::weak_ptr<Channel>(this->shared_from_this());
}
IndirectChannel::IndirectChannel(ChannelPtr channel, Channel::Type type)
: data_(std::make_unique<Data>(channel, type))
{
}
ChannelPtr IndirectChannel::get()
{
return data_->channel;
}
void IndirectChannel::reset(ChannelPtr channel)
{
assert(this->data_->type != Channel::Type::Direct);
this->data_->channel = channel;
this->data_->changed.invoke();
}
pajlada::Signals::NoArgSignal &IndirectChannel::getChannelChanged()
{
return this->data_->changed;
}
Channel::Type IndirectChannel::getType()
{
if (this->data_->type == Channel::Type::Direct) {
return this->get()->getType();
} else {
return this->data_->type;
}
}
} // namespace chatterino
+9 -43
View File
@@ -1,10 +1,7 @@
#pragma once
#include "common/CompletionModel.hpp"
#include "messages/Image.hpp"
#include "messages/LimitedQueue.hpp"
#include "messages/Message.hpp"
#include "util/ConcurrentMap.hpp"
#include <QString>
#include <QTimer>
@@ -13,7 +10,9 @@
#include <memory>
namespace chatterino {
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
class Channel : public std::enable_shared_from_this<Channel>
{
@@ -52,7 +51,6 @@ public:
void addOrReplaceTimeout(MessagePtr message);
void disableAllMessages();
void replaceMessage(MessagePtr message, MessagePtr replacement);
virtual void addRecentChatter(const MessagePtr &message);
QStringList modList;
@@ -66,11 +64,9 @@ public:
CompletionModel completionModel;
// pre c++17 polyfill
std::weak_ptr<Channel> weak_from_this();
protected:
virtual void onConnected();
virtual void addRecentChatter(const MessagePtr &message);
private:
const QString name_;
@@ -88,47 +84,17 @@ class IndirectChannel
Channel::Type type;
pajlada::Signals::NoArgSignal changed;
Data() = delete;
Data(ChannelPtr _channel, Channel::Type _type)
: channel(_channel)
, type(_type)
{
}
Data(ChannelPtr channel, Channel::Type type);
};
public:
IndirectChannel(ChannelPtr channel,
Channel::Type type = Channel::Type::Direct)
: data_(new Data(channel, type))
{
}
Channel::Type type = Channel::Type::Direct);
ChannelPtr get()
{
return data_->channel;
}
void update(ChannelPtr ptr)
{
assert(this->data_->type != Channel::Type::Direct);
this->data_->channel = ptr;
this->data_->changed.invoke();
}
pajlada::Signals::NoArgSignal &getChannelChanged()
{
return this->data_->changed;
}
Channel::Type getType()
{
if (this->data_->type == Channel::Type::Direct) {
return this->get()->getType();
} else {
return this->data_->type;
}
}
ChannelPtr get();
void reset(ChannelPtr channel);
pajlada::Signals::NoArgSignal &getChannelChanged();
Channel::Type getType();
private:
std::shared_ptr<Data> data_;
+8 -1
View File
@@ -3,7 +3,6 @@
#include "common/Aliases.hpp"
#include "common/Outcome.hpp"
#include "common/ProviderId.hpp"
#include "debug/Log.hpp"
#include <QString>
#include <QWidget>
@@ -40,4 +39,12 @@ std::weak_ptr<T> weakOf(T *element)
return element->shared_from_this();
}
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
enum class CopyMode {
Everything,
OnlyTextAndEmotes,
};
} // namespace chatterino
+83 -180
View File
@@ -2,45 +2,30 @@
#include "Application.hpp"
#include "common/Common.hpp"
#include "common/UsernameSet.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandController.hpp"
#include "debug/Benchmark.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp"
#include <QtAlgorithms>
#include <utility>
namespace chatterino {
// -- TaggedString
//
// TaggedString
//
CompletionModel::TaggedString::TaggedString(const QString &_str, Type _type)
: str(_str)
CompletionModel::TaggedString::TaggedString(const QString &_string, Type _type)
: string(_string)
, type(_type)
, timeAdded(std::chrono::steady_clock::now())
{
}
bool CompletionModel::TaggedString::isExpired(
const std::chrono::steady_clock::time_point &now) const
{
switch (this->type) {
case Type::Username: {
static std::chrono::minutes expirationTimer(10);
return (this->timeAdded + expirationTimer < now);
} break;
default: {
return false;
} break;
}
return false;
}
bool CompletionModel::TaggedString::isEmote() const
{
return this->type > Type::EmoteStart && this->type < Type::EmoteEnd;
@@ -48,35 +33,23 @@ bool CompletionModel::TaggedString::isEmote() const
bool CompletionModel::TaggedString::operator<(const TaggedString &that) const
{
if (this->isEmote()) {
if (that.isEmote()) {
int k = QString::compare(this->str, that.str, Qt::CaseInsensitive);
if (k == 0) {
return this->str > that.str;
}
return k < 0;
}
return true;
if (this->isEmote() != that.isEmote()) {
return this->isEmote();
}
if (that.isEmote()) {
return false;
}
int k = QString::compare(this->str, that.str, Qt::CaseInsensitive);
if (k == 0) {
return false;
}
// try comparing insensitively, if they are the same then senstively
// (fixes order of LuL and LUL)
int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
if (k == 0) return this->string > that.string;
return k < 0;
}
// -- CompletionModel
CompletionModel::CompletionModel(const QString &channelName)
: channelName_(channelName)
//
// CompletionModel
//
CompletionModel::CompletionModel(Channel &channel)
: channel_(channel)
{
}
@@ -87,160 +60,90 @@ int CompletionModel::columnCount(const QModelIndex &) const
QVariant CompletionModel::data(const QModelIndex &index, int) const
{
std::lock_guard<std::mutex> lock(this->emotesMutex_);
std::lock_guard<std::mutex> lock(this->itemsMutex_);
// TODO: Implement more safely
auto it = this->emotes_.begin();
auto it = this->items_.begin();
std::advance(it, index.row());
return QVariant(it->str);
return QVariant(it->string);
}
int CompletionModel::rowCount(const QModelIndex &) const
{
std::lock_guard<std::mutex> lock(this->emotesMutex_);
std::lock_guard<std::mutex> lock(this->itemsMutex_);
return this->emotes_.size();
return this->items_.size();
}
void CompletionModel::refresh()
void CompletionModel::refresh(const QString &prefix)
{
Log("[CompletionModel:{}] Refreshing...]", this->channelName_);
std::lock_guard<std::mutex> guard(this->itemsMutex_);
this->items_.clear();
auto app = getApp();
if (prefix.length() < 2) return;
// User-specific: Twitch Emotes
if (auto account = app->accounts->twitch.getCurrent()) {
for (const auto &emote : account->accessEmotes()->allEmoteNames) {
// XXX: No way to discern between a twitch global emote and sub
// emote right now
this->addString(emote.string,
TaggedString::Type::TwitchGlobalEmote);
}
}
// // Global: BTTV Global Emotes
// std::vector<QString> &bttvGlobalEmoteCodes =
// app->emotes->bttv.globalEmoteNames_; for (const auto &m :
// bttvGlobalEmoteCodes) {
// this->addString(m, TaggedString::Type::BTTVGlobalEmote);
// }
// // Global: FFZ Global Emotes
// std::vector<QString> &ffzGlobalEmoteCodes =
// app->emotes->ffz.globalEmoteCodes; for (const auto &m :
// ffzGlobalEmoteCodes) {
// this->addString(m, TaggedString::Type::FFZGlobalEmote);
// }
// Channel emotes
if (auto channel = dynamic_cast<TwitchChannel *>(
getApp()
->twitch2->getChannelOrEmptyByID(this->channelName_)
.get())) {
auto bttv = channel->accessBttvEmotes();
// auto it = bttv->begin();
// for (const auto &emote : *bttv) {
// }
// std::vector<QString> &bttvChannelEmoteCodes =
// app->emotes->bttv.channelEmoteName_[this->channelName_];
// for (const auto &m : bttvChannelEmoteCodes) {
// this->addString(m, TaggedString::Type::BTTVChannelEmote);
// }
// Channel-specific: FFZ Channel Emotes
for (const auto &emote : *channel->accessFfzEmotes()) {
this->addString(emote.second->name.string,
TaggedString::Type::FFZChannelEmote);
}
}
// Emojis
const auto &emojiShortCodes = app->emotes->emojis.shortCodes;
for (const auto &m : emojiShortCodes) {
this->addString(":" + m + ":", TaggedString::Type::Emoji);
}
// Commands
for (auto &command : app->commands->items.getVector()) {
this->addString(command.name, TaggedString::Command);
}
for (auto &command : app->commands->getDefaultTwitchCommandList()) {
this->addString(command, TaggedString::Command);
}
// Channel-specific: Usernames
// fourtf: only works with twitch chat
// auto c =
// ChannelManager::getInstance().getTwitchChannel(this->channelName);
// auto usernames = c->getUsernamesForCompletions();
// for (const auto &name : usernames) {
// assert(!name.displayName.isEmpty());
// this->addString(name.displayName);
// this->addString('@' + name.displayName);
// if (!name.localizedName.isEmpty()) {
// this->addString(name.localizedName);
// this->addString('@' + name.localizedName);
// }
// }
}
void CompletionModel::addString(const QString &str, TaggedString::Type type)
{
std::lock_guard<std::mutex> lock(this->emotesMutex_);
// Always add a space at the end of completions
this->emotes_.insert({str + " ", type});
}
void CompletionModel::addUser(const QString &username)
{
auto add = [this](const QString &str) {
auto ts = this->createUser(str + " ");
// Always add a space at the end of completions
std::pair<std::set<TaggedString>::iterator, bool> p =
this->emotes_.insert(ts);
if (!p.second) {
// No inseration was made, figure out if we need to replace the
// username.
if (p.first->str > ts.str) {
// Replace lowercase version of name with mixed-case version
this->emotes_.erase(p.first);
auto result2 = this->emotes_.insert(ts);
assert(result2.second);
} else {
p.first->timeAdded = std::chrono::steady_clock::now();
}
}
auto addString = [&](const QString &str, TaggedString::Type type) {
if (str.startsWith(prefix, Qt::CaseInsensitive))
this->items_.emplace(str + " ", type);
};
add(username);
add("@" + username);
}
if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_)) {
// account emotes
if (auto account = getApp()->accounts->twitch.getCurrent()) {
for (const auto &emote : account->accessEmotes()->allEmoteNames) {
// XXX: No way to discern between a twitch global emote and sub
// emote right now
addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
}
}
void CompletionModel::clearExpiredStrings()
{
std::lock_guard<std::mutex> lock(this->emotesMutex_);
// Usernames
if (prefix.length() >= UsernameSet::PrefixLength) {
auto usernames = channel->accessChatters();
auto now = std::chrono::steady_clock::now();
for (const auto &name : usernames->subrange(Prefix(prefix))) {
addString(name, TaggedString::Type::Username);
addString("@" + name, TaggedString::Type::Username);
}
}
for (auto it = this->emotes_.begin(); it != this->emotes_.end();) {
const auto &taggedString = *it;
// Bttv Global
for (auto &emote : *channel->globalBttv().emotes()) {
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
}
if (taggedString.isExpired(now)) {
// Log("String {} expired", taggedString.str);
it = this->emotes_.erase(it);
} else {
++it;
// Ffz Global
for (auto &emote : *channel->globalFfz().emotes()) {
addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
}
// Bttv Channel
for (auto &emote : *channel->bttvEmotes()) {
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
}
// Ffz Channel
for (auto &emote : *channel->ffzEmotes()) {
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
}
// Emojis
if (prefix.startsWith(":")) {
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
for (auto &m : emojiShortCodes) {
addString(":" + m + ":", TaggedString::Type::Emoji);
}
}
// Commands
for (auto &command : getApp()->commands->items.getVector()) {
addString(command.name, TaggedString::Command);
}
for (auto &command :
getApp()->commands->getDefaultTwitchCommandList()) {
addString(command, TaggedString::Command);
}
}
}
CompletionModel::TaggedString CompletionModel::createUser(const QString &str)
{
return TaggedString{str, TaggedString::Type::Username};
}
} // namespace chatterino
+8 -19
View File
@@ -1,7 +1,5 @@
#pragma once
#include "common/Common.hpp"
#include <QAbstractListModel>
#include <chrono>
@@ -10,7 +8,7 @@
namespace chatterino {
class TwitchChannel;
class Channel;
class CompletionModel : public QAbstractListModel
{
@@ -33,39 +31,30 @@ class CompletionModel : public QAbstractListModel
Command,
};
TaggedString(const QString &_str, Type _type);
TaggedString(const QString &string, Type type);
bool isExpired(const std::chrono::steady_clock::time_point &now) const;
bool isEmote() const;
bool operator<(const TaggedString &that) const;
QString str;
// Type will help decide the lifetime of the tagged strings
QString string;
Type type;
mutable std::chrono::steady_clock::time_point timeAdded;
};
public:
CompletionModel(const QString &channelName);
CompletionModel(Channel &channel);
virtual int columnCount(const QModelIndex &) const override;
virtual QVariant data(const QModelIndex &index, int) const override;
virtual int rowCount(const QModelIndex &) const override;
void refresh();
void addString(const QString &str, TaggedString::Type type);
void addUser(const QString &str);
void clearExpiredStrings();
void refresh(const QString &prefix);
private:
TaggedString createUser(const QString &str);
mutable std::mutex emotesMutex_;
std::set<TaggedString> emotes_;
QString channelName_;
std::set<TaggedString> items_;
mutable std::mutex itemsMutex_;
Channel &channel_;
};
} // namespace chatterino
-46
View File
@@ -1,46 +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
-27
View File
@@ -1,27 +0,0 @@
#pragma once
#include "messages/Image.hpp"
#include "util/ConcurrentMap.hpp"
namespace chatterino {
// struct EmoteData {
// EmoteData() = default;
// EmoteData(Image *image);
// // Emotes must have a 1x image to be valid
// bool isValid() const;
// Image *getImage(float scale) const;
// // Link to the emote page i.e.
// https://www.frankerfacez.com/emoticon/144722-pajaCringe QString pageLink;
// Image *image1x = nullptr;
// Image *image2x = nullptr;
// Image *image3x = nullptr;
//};
// using EmoteMap = ConcurrentMap<QString, EmoteData>;
} // namespace chatterino
+10 -28
View File
@@ -4,19 +4,17 @@
namespace chatterino {
// = std::enable_if<std::is_enum<T>::value>::type
template <typename T, typename Q = typename std::underlying_type<T>::type>
class FlagsEnum
{
public:
FlagsEnum()
: value(static_cast<T>(0))
: value_(static_cast<T>(0))
{
}
FlagsEnum(T value)
: value(value)
: value_(value)
{
}
@@ -29,22 +27,22 @@ public:
bool operator==(const FlagsEnum<T> &other)
{
return this->value == other.value;
return this->value_ == other.value_;
}
bool operator!=(const FlagsEnum &other)
{
return this->value != other.value;
return this->value_ != other.value_;
}
void set(T flag)
{
reinterpret_cast<Q &>(this->value) |= static_cast<Q>(flag);
reinterpret_cast<Q &>(this->value_) |= static_cast<Q>(flag);
}
void unset(T flag)
{
reinterpret_cast<Q &>(this->value) &= ~static_cast<Q>(flag);
reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag);
}
void set(T flag, bool value)
@@ -57,33 +55,17 @@ public:
bool has(T flag) const
{
return static_cast<Q>(this->value) & static_cast<Q>(flag);
return static_cast<Q>(this->value_) & static_cast<Q>(flag);
}
// bool hasAny(std::initializer_list<T> flags) const
//{
// for (auto flag : flags) {
// if (this->has(flag)) return true;
// }
// return false;
//}
bool hasAny(FlagsEnum flags) const
{
return static_cast<Q>(this->value) & static_cast<Q>(flags.value);
return static_cast<Q>(this->value_) & static_cast<Q>(flags.value_);
}
// bool hasAll(std::initializer_list<T> flags) const
//{
// for (auto flag : flags) {
// if (!this->has(flag)) return false;
// }
// return true;
//}
bool hasAll(FlagsEnum<T> flags) const
{
return (static_cast<Q>(this->value) & static_cast<Q>(flags.value)) &&
return (static_cast<Q>(this->value_) & static_cast<Q>(flags.value_)) &&
static_cast<Q>(flags->value);
}
@@ -93,7 +75,7 @@ public:
}
private:
T value;
T value_{};
};
} // namespace chatterino
+45 -51
View File
@@ -5,69 +5,63 @@
#include <QString>
#include <QTextStream>
// ip 0.0.0.0 - 224.0.0.0
#define IP \
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" \
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" \
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
#define PORT "(?::\\d{2,5})"
#define WEB_CHAR1 "[_a-z\\x{00a1}-\\x{ffff}0-9]"
#define WEB_CHAR2 "[a-z\\x{00a1}-\\x{ffff}0-9]"
#define SPOTIFY_1 "(?:artist|album|track|user:[^:]+:playlist):[a-zA-Z0-9]+"
#define SPOTIFY_2 "user:[^:]+"
#define SPOTIFY_3 "search:(?:[-\\w$\\.+!*'(),]+|%[a-fA-F0-9]{2})+"
#define SPOTIFY_PARAMS "(?:" SPOTIFY_1 "|" SPOTIFY_2 "|" SPOTIFY_3 ")"
#define SPOTIFY_LINK "(?x-mi:(spotify:" SPOTIFY_PARAMS "))"
#define WEB_PROTOCOL "(?:(?:https?|ftps?)://)?"
#define WEB_USER "(?:\\S+(?::\\S*)?@)?"
#define WEB_HOST "(?:(?:" WEB_CHAR1 "-*)*" WEB_CHAR2 "+)"
#define WEB_DOMAIN "(?:\\.(?:" WEB_CHAR2 "-*)*" WEB_CHAR2 "+)*"
#define WEB_TLD "(?:" + tldData + ")"
#define WEB_RESOURCE_PATH "(?:[/?#]\\S*)"
#define WEB_LINK \
WEB_PROTOCOL WEB_USER "(?:" IP "|" WEB_HOST WEB_DOMAIN "\\." WEB_TLD PORT \
"?" WEB_RESOURCE_PATH "?)"
#define LINK "^(?:" SPOTIFY_LINK "|" WEB_LINK ")$"
namespace chatterino {
LinkParser::LinkParser(const QString &unparsedString)
{
static QRegularExpression linkRegex = [] {
static QRegularExpression newLineRegex("\r?\n");
QFile tldFile(":/tlds.txt");
tldFile.open(QFile::ReadOnly);
QFile file(":/tlds.txt");
file.open(QFile::ReadOnly);
QTextStream tlds(&file);
tlds.setCodec("UTF-8");
QTextStream t1(&tldFile);
t1.setCodec("UTF-8");
// tldData gets injected into the LINK macro
auto tldData = tlds.readAll().replace(newLineRegex, "|");
(void)tldData;
// Read the TLDs in and replace the newlines with pipes
QString tldData = t1.readAll().replace(newLineRegex, "|");
const QString hyperlinkRegExp =
"^"
// Identifier for spotify
"(?x-mi:(spotify:(?:"
"(?:artist|album|track|user:[^:]+:playlist):"
"[a-zA-Z0-9]+|user:[^:]+|search:"
"(?:[-\\w$\\.+!*'(),]+|%[a-fA-F0-9]{2})+)))"
// If nothing matches then just go on
"|"
"^"
// Identifier for http and ftp
"(?:(?:https?|ftps?)://)?"
// user:pass authentication
"(?:\\S+(?::\\S*)?@)?"
"(?:"
// IP address dotted notation octets
// excludes loopback network 0.0.0.0
// excludes reserved space >= 224.0.0.0
// excludes network & broacast addresses
// (first & last IP address of each class)
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])"
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}"
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
"|"
// host name
"(?:(?:[_a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-9]+"
")"
// domain name
"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-"
"9]+)*"
// TLD identifier
//"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}]{2,}))"
"(?:[\\.](?:" +
tldData +
"))"
"\\.?"
")"
// port number
"(?::\\d{2,5})?"
// resource path
"(?:[/?#]\\S*)?"
"$";
return QRegularExpression(hyperlinkRegExp,
return QRegularExpression(LINK,
QRegularExpression::CaseInsensitiveOption);
}();
this->match_ = linkRegex.match(unparsedString);
}
bool LinkParser::hasMatch() const
{
return this->match_.hasMatch();
}
QString LinkParser::getCaptured() const
{
return this->match_.captured();
}
} // namespace chatterino
+2 -9
View File
@@ -10,15 +10,8 @@ class LinkParser
public:
explicit LinkParser(const QString &unparsedString);
bool hasMatch() const
{
return this->match_.hasMatch();
}
QString getCaptured() const
{
return this->match_.captured();
}
bool hasMatch() const;
QString getCaptured() const;
private:
QRegularExpressionMatch match_;
-38
View File
@@ -1,38 +0,0 @@
#pragma once
#include <mutex>
namespace chatterino {
template <typename Type>
class LockedObject
{
public:
LockedObject &operator=(const LockedObject<Type> &other)
{
this->mutex_.lock();
this->data = other.getValue();
this->mutex_.unlock();
return *this;
}
LockedObject &operator=(const Type &other)
{
this->mutex_.lock();
this->data = other;
this->mutex_.unlock();
return *this;
}
private:
Type value_;
std::mutex mutex_;
};
} // namespace chatterino
+1 -2
View File
@@ -2,12 +2,11 @@
#include <functional>
#include "Common.hpp"
class QNetworkReply;
namespace chatterino {
class Outcome;
class NetworkResult;
using NetworkSuccessCallback = std::function<Outcome(NetworkResult)>;
+1 -4
View File
@@ -1,6 +1,5 @@
#include "common/NetworkData.hpp"
#include "Application.hpp"
#include "singletons/Paths.hpp"
#include "util/DebugCount.hpp"
@@ -42,9 +41,7 @@ QString NetworkData::getHash()
void NetworkData::writeToCache(const QByteArray &bytes)
{
if (this->useQuickLoadCache_) {
auto app = getApp();
QFile cachedFile(app->paths->cacheDirectory + "/" + this->getHash());
QFile cachedFile(getPaths()->cacheDirectory() + "/" + this->getHash());
if (cachedFile.open(QIODevice::WriteOnly)) {
cachedFile.write(bytes);
+1
View File
@@ -19,6 +19,7 @@ struct NetworkData {
QNetworkRequest request_;
const QObject *caller_ = nullptr;
bool useQuickLoadCache_{};
bool executeConcurrently{};
NetworkReplyCreatedCallback onReplyCreated_;
NetworkErrorCallback onError_;
+2 -2
View File
@@ -5,11 +5,11 @@
namespace chatterino {
QThread NetworkManager::workerThread;
QNetworkAccessManager NetworkManager::NaM;
QNetworkAccessManager NetworkManager::accessManager;
void NetworkManager::init()
{
NetworkManager::NaM.moveToThread(&NetworkManager::workerThread);
NetworkManager::accessManager.moveToThread(&NetworkManager::workerThread);
NetworkManager::workerThread.start();
}
+1 -10
View File
@@ -1,16 +1,7 @@
#pragma once
#include "common/NetworkRequester.hpp"
#include "common/NetworkWorker.hpp"
#include "debug/Log.hpp"
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QThread>
#include <QTimer>
#include <QUrl>
namespace chatterino {
@@ -20,7 +11,7 @@ class NetworkManager : public QObject
public:
static QThread workerThread;
static QNetworkAccessManager NaM;
static QNetworkAccessManager accessManager;
static void init();
static void deinit();
+29 -12
View File
@@ -1,13 +1,16 @@
#include "common/NetworkRequest.hpp"
#include "Application.hpp"
#include "common/NetworkData.hpp"
#include "common/NetworkManager.hpp"
#include "common/Outcome.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchCommon.hpp"
#include "singletons/Paths.hpp"
#include "util/DebugCount.hpp"
#include <QFile>
#include <QtConcurrent>
#include <cassert>
namespace chatterino {
@@ -80,6 +83,11 @@ void NetworkRequest::setTimeout(int ms)
this->timer->timeoutMS_ = ms;
}
void NetworkRequest::setExecuteConcurrently(bool value)
{
this->data->executeConcurrently = value;
}
void NetworkRequest::makeAuthorizedV5(const QString &clientID,
const QString &oauthToken)
{
@@ -130,16 +138,15 @@ void NetworkRequest::execute()
} break;
default: {
Log("[Execute] Unhandled request type");
log("[Execute] Unhandled request type");
} break;
}
}
Outcome NetworkRequest::tryLoadCachedFile()
{
auto app = getApp();
QFile cachedFile(app->paths->cacheDirectory + "/" + this->data->getHash());
QFile cachedFile(getPaths()->cacheDirectory() + "/" +
this->data->getHash());
if (!cachedFile.exists()) {
// File didn't exist
@@ -180,14 +187,15 @@ void NetworkRequest::doRequest()
auto reply = [&]() -> QNetworkReply * {
switch (data->requestType_) {
case NetworkRequestType::Get:
return NetworkManager::NaM.get(data->request_);
return NetworkManager::accessManager.get(data->request_);
case NetworkRequestType::Put:
return NetworkManager::NaM.put(data->request_,
data->payload_);
return NetworkManager::accessManager.put(data->request_,
data->payload_);
case NetworkRequestType::Delete:
return NetworkManager::NaM.deleteResource(data->request_);
return NetworkManager::accessManager.deleteResource(
data->request_);
default:
return nullptr;
@@ -195,13 +203,13 @@ void NetworkRequest::doRequest()
}();
if (reply == nullptr) {
Log("Unhandled request type");
log("Unhandled request type");
return;
}
if (timer->isStarted()) {
timer->onTimeout(worker, [reply, data]() {
Log("Aborted!");
log("Aborted!");
reply->abort();
if (data->onError_) {
data->onError_(-2);
@@ -228,7 +236,16 @@ void NetworkRequest::doRequest()
NetworkResult result(bytes);
DebugCount::increase("http request success");
data->onSuccess_(result);
// log("starting {}", data->request_.url().toString());
if (data->onSuccess_) {
if (data->executeConcurrently)
QtConcurrent::run(
[onSuccess = std::move(data->onSuccess_),
result = std::move(result)] { onSuccess(result); });
else
data->onSuccess_(result);
}
// log("finished {}", data->request_.url().toString());
reply->deleteLater();
};
+5 -11
View File
@@ -1,14 +1,13 @@
#pragma once
#include "Application.hpp"
#include "common/NetworkCommon.hpp"
#include "common/NetworkData.hpp"
#include "common/NetworkRequester.hpp"
#include "common/NetworkResult.hpp"
#include "common/NetworkTimer.hpp"
#include "common/NetworkWorker.hpp"
namespace chatterino {
class NetworkData;
class NetworkRequest
{
@@ -27,19 +26,15 @@ class NetworkRequest
bool executed_ = false;
public:
NetworkRequest() = delete;
NetworkRequest(const NetworkRequest &other) = delete;
NetworkRequest &operator=(const NetworkRequest &other) = delete;
NetworkRequest(NetworkRequest &&other) = default;
NetworkRequest &operator=(NetworkRequest &&other) = default;
explicit NetworkRequest(
const std::string &url,
NetworkRequestType requestType = NetworkRequestType::Get);
explicit NetworkRequest(
QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
NetworkRequest(NetworkRequest &&other) = default;
NetworkRequest &operator=(NetworkRequest &&other) = default;
~NetworkRequest();
void setRequestType(NetworkRequestType newRequestType);
@@ -55,14 +50,13 @@ public:
void setRawHeader(const char *headerName, const QByteArray &value);
void setRawHeader(const char *headerName, const QString &value);
void setTimeout(int ms);
void setExecuteConcurrently(bool value);
void makeAuthorizedV5(const QString &clientID,
const QString &oauthToken = QString());
void execute();
private:
// Returns true if the file was successfully loaded from cache
// Returns false if the cache file either didn't exist, or it contained
// "invalid" data "invalid" is specified by the onSuccess callback
Outcome tryLoadCachedFile();
+1 -1
View File
@@ -31,7 +31,7 @@ rapidjson::Document NetworkResult::parseRapidJson() const
ret.Parse(this->data_.data(), this->data_.length());
if (result.Code() != rapidjson::kParseErrorNone) {
Log("JSON parse error: {} ({})",
log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset());
return ret;
}
-34
View File
@@ -1,34 +0,0 @@
#pragma once
#include "boost/noncopyable.hpp"
namespace chatterino {
template <typename T>
class Property final : boost::noncopyable
{
public:
Property()
{
}
Property(const T &value)
: value_(value)
{
}
T &operator=(const T &f)
{
return value_ = f;
}
operator T const &() const
{
return value_;
}
protected:
T value_;
};
} // namespace chatterino
-43
View File
@@ -1,43 +0,0 @@
#pragma once
#include <QString>
#include <pajlada/settings/serialize.hpp>
namespace pajlada {
namespace Settings {
template <>
struct Serialize<QString> {
static rapidjson::Value get(const QString &value,
rapidjson::Document::AllocatorType &a)
{
return rapidjson::Value(value.toUtf8(), a);
}
};
template <>
struct Deserialize<QString> {
static QString get(const rapidjson::Value &value, bool *error = nullptr)
{
if (!value.IsString()) {
PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION(
"Deserialized rapidjson::Value is not a string");
return QString{};
}
try {
return QString::fromUtf8(value.GetString(),
value.GetStringLength());
} catch (const std::exception &) {
// int x = 5;
} catch (...) {
// int y = 5;
}
return QString{};
}
};
} // namespace Settings
} // namespace pajlada
-34
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
+5 -5
View File
@@ -46,16 +46,15 @@ public:
}
private:
T *element_;
std::mutex *mutex_;
bool isValid_ = true;
T *element_{};
std::mutex *mutex_{};
bool isValid_{true};
};
template <typename T>
class UniqueAccess
{
public:
// template <typename X = decltype(T())>
UniqueAccess()
: element_(T())
{
@@ -88,7 +87,8 @@ public:
return AccessGuard<T>(this->element_, this->mutex_);
}
template <typename X = T, typename = std::enable_if_t<!std::is_const_v<X>>>
template <typename X = T,
typename = std::enable_if_t<!std::is_const<X>::value>>
AccessGuard<const X> accessConst() const
{
return AccessGuard<const T>(this->element_, this->mutex_);
+112
View File
@@ -0,0 +1,112 @@
#include "UsernameSet.hpp"
#include <tuple>
namespace chatterino {
//
// UsernameSet
//
UsernameSet::ConstIterator UsernameSet::begin() const
{
return this->items.begin();
}
UsernameSet::ConstIterator UsernameSet::end() const
{
return this->items.end();
}
UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const
{
auto it = this->firstKeyForPrefix.find(prefix);
if (it != this->firstKeyForPrefix.end()) {
auto start = this->items.find(it->second);
auto end = start;
while (end != this->items.end() && prefix.isStartOf(*end)) {
end++;
}
return {start, end};
}
return {this->items.end(), this->items.end()};
}
std::set<QString>::size_type UsernameSet::size() const
{
return this->items.size();
}
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(const QString &value)
{
this->insertPrefix(value);
return this->items.insert(value);
}
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(QString &&value)
{
this->insertPrefix(value);
return this->items.insert(std::move(value));
}
void UsernameSet::insertPrefix(const QString &value)
{
auto &string = this->firstKeyForPrefix[Prefix(value)];
if (string.isNull() || value < string) string = value;
}
//
// Range
//
UsernameSet::Range::Range(ConstIterator start, ConstIterator end)
: start_(start)
, end_(end)
{
}
UsernameSet::ConstIterator UsernameSet::Range::begin()
{
return this->start_;
}
UsernameSet::ConstIterator UsernameSet::Range::end()
{
return this->end_;
}
//
// Prefix
//
Prefix::Prefix(const QString &string)
: first(string.size() >= 1 ? string[0].toLower() : '\0')
, second(string.size() >= 2 ? string[1].toLower() : '\0')
{
}
bool Prefix::operator==(const Prefix &other) const
{
return std::tie(this->first, this->second) ==
std::tie(other.first, other.second);
}
bool Prefix::isStartOf(const QString &string) const
{
if (string.size() == 0) {
return this->first == QChar('\0') && this->second == QChar('\0');
} else if (string.size() == 1) {
return this->first == string[0].toLower() &&
this->second == QChar('\0');
} else {
return this->first == string[0].toLower() &&
this->second == string[1].toLower();
}
}
} // namespace chatterino
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include <QString>
#include <functional>
#include <set>
#include <unordered_map>
namespace chatterino {
class Prefix
{
public:
Prefix(const QString &string);
bool operator==(const Prefix &other) const;
bool isStartOf(const QString &string) const;
private:
QChar first;
QChar second;
friend struct std::hash<Prefix>;
};
} // namespace chatterino
namespace std {
template <>
struct hash<chatterino::Prefix> {
size_t operator()(const chatterino::Prefix &prefix) const
{
return (size_t(prefix.first.unicode()) << 16) |
size_t(prefix.second.unicode());
}
};
} // namespace std
namespace chatterino {
class UsernameSet
{
public:
static constexpr int PrefixLength = 2;
using Iterator = std::set<QString>::iterator;
using ConstIterator = std::set<QString>::const_iterator;
class Range
{
public:
Range(ConstIterator start, ConstIterator end);
ConstIterator begin();
ConstIterator end();
private:
ConstIterator start_;
ConstIterator end_;
};
ConstIterator begin() const;
ConstIterator end() const;
Range subrange(const Prefix &prefix) const;
std::set<QString>::size_type size() const;
std::pair<Iterator, bool> insert(const QString &value);
std::pair<Iterator, bool> insert(QString &&value);
private:
void insertPrefix(const QString &string);
std::set<QString> items;
std::unordered_map<Prefix, QString> firstKeyForPrefix;
};
} // namespace chatterino
+3 -3
View File
@@ -5,9 +5,9 @@
#define CHATTERINO_VERSION "2.0.4"
#if defined(Q_OS_WIN)
#define CHATTERINO_OS "win"
# define CHATTERINO_OS "win"
#elif defined(Q_OS_MACOS)
#define CHATTERINO_OS "macos"
# define CHATTERINO_OS "macos"
#elif defined(Q_OS_LINUX)
#define CHATTERINO_OS "linux"
# define CHATTERINO_OS "linux"
#endif
@@ -1,6 +1,8 @@
#include "AccountController.hpp"
#include "controllers/accounts/Account.hpp"
#include "controllers/accounts/AccountModel.hpp"
#include "providers/twitch/TwitchAccount.hpp"
namespace chatterino {
@@ -1,16 +1,15 @@
#pragma once
#include "common/Singleton.hpp"
#include <QObject>
#include "common/SignalVector.hpp"
#include "controllers/accounts/Account.hpp"
#include "common/Singleton.hpp"
#include "providers/twitch/TwitchAccountManager.hpp"
#include "util/SharedPtrElementLess.hpp"
namespace chatterino {
class Account;
class Settings;
class Paths;
@@ -1,5 +1,6 @@
#include "AccountModel.hpp"
#include "controllers/accounts/Account.hpp"
#include "util/StandardItemHelper.hpp"
namespace chatterino {
@@ -8,6 +8,7 @@
namespace chatterino {
class Account;
class AccountController;
class AccountModel : public SignalVectorModel<std::shared_ptr<Account>>
@@ -5,7 +5,10 @@
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/Command.hpp"
#include "controllers/commands/CommandModel.hpp"
#include "debug/Log.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "messages/MessageElement.hpp"
#include "providers/twitch/TwitchApi.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchServer.hpp"
@@ -78,7 +81,7 @@ void CommandController::save()
{
QFile textFile(this->filePath_);
if (!textFile.open(QIODevice::WriteOnly)) {
Log("[CommandController::saveCommands] Unable to open {} for writing",
log("[CommandController::saveCommands] Unable to open {} for writing",
this->filePath_);
return;
}
@@ -1,6 +1,6 @@
#pragma once
#include "common/SerializeCustom.hpp"
#include "util/RapidJsonSerializeQString.hpp"
#include "util/RapidjsonHelpers.hpp"
#include <QRegularExpression>
@@ -68,37 +68,39 @@ private:
namespace pajlada {
namespace Settings {
template <>
struct Serialize<chatterino::HighlightBlacklistUser> {
static rapidjson::Value get(const chatterino::HighlightBlacklistUser &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
template <>
struct Serialize<chatterino::HighlightBlacklistUser> {
static rapidjson::Value get(
const chatterino::HighlightBlacklistUser &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
AddMember(ret, "pattern", value.getPattern(), a);
AddMember(ret, "regex", value.isRegex(), a);
AddMember(ret, "pattern", value.getPattern(), a);
AddMember(ret, "regex", value.isRegex(), a);
return ret;
}
};
return ret;
}
};
template <>
struct Deserialize<chatterino::HighlightBlacklistUser> {
static chatterino::HighlightBlacklistUser get(const rapidjson::Value &value)
{
QString pattern;
bool isRegex = false;
template <>
struct Deserialize<chatterino::HighlightBlacklistUser> {
static chatterino::HighlightBlacklistUser get(
const rapidjson::Value &value)
{
QString pattern;
bool isRegex = false;
if (!value.IsObject()) {
return chatterino::HighlightBlacklistUser(pattern, isRegex);
}
chatterino::rj::getSafe(value, "pattern", pattern);
chatterino::rj::getSafe(value, "regex", isRegex);
if (!value.IsObject()) {
return chatterino::HighlightBlacklistUser(pattern, isRegex);
}
chatterino::rj::getSafe(value, "pattern", pattern);
chatterino::rj::getSafe(value, "regex", isRegex);
return chatterino::HighlightBlacklistUser(pattern, isRegex);
}
};
};
} // namespace Settings
} // namespace pajlada
@@ -1,15 +1,16 @@
#pragma once
#include "common/Singleton.hpp"
#include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp"
#include "common/Singleton.hpp"
#include "controllers/highlights/HighlightBlacklistUser.hpp"
#include "controllers/highlights/HighlightPhrase.hpp"
#include "messages/Message.hpp"
#include "singletons/Settings.hpp"
namespace chatterino {
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
class Settings;
class Paths;
+9 -12
View File
@@ -37,13 +37,13 @@ void HighlightModel::getRowFromItem(const HighlightPhrase &item,
void HighlightModel::afterInit()
{
std::vector<QStandardItem *> row = this->createRow();
setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(),
true, false);
setBoolItem(row[0], getSettings()->enableHighlightsSelf.getValue(), true,
false);
row[0]->setData("Your username (automatic)", Qt::DisplayRole);
setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(),
true, false);
setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(),
true, false);
setBoolItem(row[1], getSettings()->enableHighlightTaskbar.getValue(), true,
false);
setBoolItem(row[2], getSettings()->enableHighlightSound.getValue(), true,
false);
row[3]->setFlags(0);
this->insertCustomRow(row, 0);
}
@@ -55,20 +55,17 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
switch (column) {
case 0: {
if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightsSelf.setValue(
value.toBool());
getSettings()->enableHighlightsSelf.setValue(value.toBool());
}
} break;
case 1: {
if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightTaskbar.setValue(
value.toBool());
getSettings()->enableHighlightTaskbar.setValue(value.toBool());
}
} break;
case 2: {
if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightSound.setValue(
value.toBool());
getSettings()->enableHighlightSound.setValue(value.toBool());
}
} break;
case 3: {
+34 -32
View File
@@ -1,6 +1,6 @@
#pragma once
#include "common/SerializeCustom.hpp"
#include "util/RapidJsonSerializeQString.hpp"
#include "util/RapidjsonHelpers.hpp"
#include <QRegularExpression>
@@ -72,43 +72,45 @@ private:
namespace pajlada {
namespace Settings {
template <>
struct Serialize<chatterino::HighlightPhrase> {
static rapidjson::Value get(const chatterino::HighlightPhrase &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
template <>
struct Serialize<chatterino::HighlightPhrase> {
static rapidjson::Value get(const chatterino::HighlightPhrase &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
AddMember(ret, "pattern", value.getPattern(), a);
AddMember(ret, "alert", value.getAlert(), a);
AddMember(ret, "sound", value.getSound(), a);
AddMember(ret, "regex", value.isRegex(), a);
AddMember(ret, "pattern", value.getPattern(), a);
AddMember(ret, "alert", value.getAlert(), a);
AddMember(ret, "sound", value.getSound(), a);
AddMember(ret, "regex", value.isRegex(), a);
return ret;
}
};
template <>
struct Deserialize<chatterino::HighlightPhrase> {
static chatterino::HighlightPhrase get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::HighlightPhrase(QString(), true, false, false);
return ret;
}
};
QString _pattern;
bool _alert = true;
bool _sound = false;
bool _isRegex = false;
template <>
struct Deserialize<chatterino::HighlightPhrase> {
static chatterino::HighlightPhrase get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::HighlightPhrase(QString(), true, false,
false);
}
chatterino::rj::getSafe(value, "pattern", _pattern);
chatterino::rj::getSafe(value, "alert", _alert);
chatterino::rj::getSafe(value, "sound", _sound);
chatterino::rj::getSafe(value, "regex", _isRegex);
QString _pattern;
bool _alert = true;
bool _sound = false;
bool _isRegex = false;
return chatterino::HighlightPhrase(_pattern, _alert, _sound, _isRegex);
}
};
chatterino::rj::getSafe(value, "pattern", _pattern);
chatterino::rj::getSafe(value, "alert", _alert);
chatterino::rj::getSafe(value, "sound", _sound);
chatterino::rj::getSafe(value, "regex", _isRegex);
return chatterino::HighlightPhrase(_pattern, _alert, _sound,
_isRegex);
}
};
} // namespace Settings
} // namespace pajlada
+2 -3
View File
@@ -1,10 +1,9 @@
#pragma once
#include "common/Singleton.hpp"
#include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp"
#include "common/Singleton.hpp"
#include "controllers/ignores/IgnorePhrase.hpp"
#include "singletons/Settings.hpp"
namespace chatterino {
+26 -26
View File
@@ -1,6 +1,6 @@
#pragma once
#include "common/SerializeCustom.hpp"
#include "util/RapidJsonSerializeQString.hpp"
#include "util/RapidjsonHelpers.hpp"
#include <QRegularExpression>
@@ -59,37 +59,37 @@ private:
namespace pajlada {
namespace Settings {
template <>
struct Serialize<chatterino::IgnorePhrase> {
static rapidjson::Value get(const chatterino::IgnorePhrase &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
template <>
struct Serialize<chatterino::IgnorePhrase> {
static rapidjson::Value get(const chatterino::IgnorePhrase &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
AddMember(ret, "pattern", value.getPattern(), a);
AddMember(ret, "regex", value.isRegex(), a);
AddMember(ret, "pattern", value.getPattern(), a);
AddMember(ret, "regex", value.isRegex(), a);
return ret;
}
};
template <>
struct Deserialize<chatterino::IgnorePhrase> {
static chatterino::IgnorePhrase get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::IgnorePhrase(QString(), false);
return ret;
}
};
QString _pattern;
bool _isRegex = false;
template <>
struct Deserialize<chatterino::IgnorePhrase> {
static chatterino::IgnorePhrase get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::IgnorePhrase(QString(), false);
}
chatterino::rj::getSafe(value, "pattern", _pattern);
chatterino::rj::getSafe(value, "regex", _isRegex);
QString _pattern;
bool _isRegex = false;
return chatterino::IgnorePhrase(_pattern, _isRegex);
}
};
chatterino::rj::getSafe(value, "pattern", _pattern);
chatterino::rj::getSafe(value, "regex", _isRegex);
return chatterino::IgnorePhrase(_pattern, _isRegex);
}
};
} // namespace Settings
} // namespace pajlada
@@ -2,6 +2,7 @@
#include <QRegularExpression>
#include "Application.hpp"
#include "messages/Image.hpp"
#include "singletons/Resources.hpp"
namespace chatterino {
@@ -60,8 +61,7 @@ ModerationAction::ModerationAction(const QString &action)
// str);
// }
} else if (action.startsWith("/ban ")) {
this->image_ =
Image::fromNonOwningPixmap(&getApp()->resources->buttons.ban);
this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban);
} else {
QString xD = action;
@@ -4,11 +4,13 @@
#include <boost/optional.hpp>
#include <pajlada/settings/serialize.hpp>
#include "messages/Image.hpp"
#include "util/RapidjsonHelpers.hpp"
namespace chatterino {
class Image;
using ImagePtr = std::shared_ptr<Image>;
class ModerationAction
{
public:
@@ -34,34 +36,34 @@ private:
namespace pajlada {
namespace Settings {
template <>
struct Serialize<chatterino::ModerationAction> {
static rapidjson::Value get(const chatterino::ModerationAction &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
template <>
struct Serialize<chatterino::ModerationAction> {
static rapidjson::Value get(const chatterino::ModerationAction &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
AddMember(ret, "pattern", value.getAction(), a);
AddMember(ret, "pattern", value.getAction(), a);
return ret;
}
};
template <>
struct Deserialize<chatterino::ModerationAction> {
static chatterino::ModerationAction get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::ModerationAction(QString());
return ret;
}
};
QString pattern;
template <>
struct Deserialize<chatterino::ModerationAction> {
static chatterino::ModerationAction get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::ModerationAction(QString());
}
chatterino::rj::getSafe(value, "pattern", pattern);
QString pattern;
return chatterino::ModerationAction(pattern);
}
};
chatterino::rj::getSafe(value, "pattern", pattern);
return chatterino::ModerationAction(pattern);
}
};
} // namespace Settings
} // namespace pajlada
@@ -17,12 +17,16 @@ void ModerationActions::initialize(Settings &settings, Paths &paths)
assert(!this->initialized_);
this->initialized_ = true;
for (auto &val : this->setting_.getValue()) {
this->setting_ =
std::make_unique<ChatterinoSetting<std::vector<ModerationAction>>>(
"/moderation/actions");
for (auto &val : this->setting_->getValue()) {
this->items.insertItem(val);
}
this->items.delayedItemsChanged.connect([this] { //
this->setting_.setValue(this->items.getVector());
this->setting_->setValue(this->items.getVector());
});
}
@@ -25,8 +25,7 @@ public:
ModerationActionModel *createModel(QObject *parent);
private:
ChatterinoSetting<std::vector<ModerationAction>> setting_ = {
"/moderation/actions"};
std::unique_ptr<ChatterinoSetting<std::vector<ModerationAction>>> setting_;
bool initialized_ = false;
};
@@ -29,12 +29,12 @@ void TaggedUsersModel::afterInit()
{
// std::vector<QStandardItem *> row = this->createRow();
// setBoolItem(row[0],
// getApp()->settings->enableHighlightsSelf.getValue(), true, false);
// getSettings()->enableHighlightsSelf.getValue(), true, false);
// row[0]->setData("Your username (automatic)", Qt::DisplayRole);
// setBoolItem(row[1],
// getApp()->settings->enableHighlightTaskbar.getValue(), true, false);
// getSettings()->enableHighlightTaskbar.getValue(), true, false);
// setBoolItem(row[2],
// getApp()->settings->enableHighlightSound.getValue(), true, false);
// getSettings()->enableHighlightSound.getValue(), true, false);
// row[3]->setFlags(0); this->insertCustomRow(row, 0);
}
@@ -45,17 +45,17 @@ void TaggedUsersModel::afterInit()
// switch (column) {
// case 0: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightsSelf.setValue(value.toBool());
// getSettings()->enableHighlightsSelf.setValue(value.toBool());
// }
// } break;
// case 1: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightTaskbar.setValue(value.toBool());
// getSettings()->enableHighlightTaskbar.setValue(value.toBool());
// }
// } break;
// case 2: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightSound.setValue(value.toBool());
// getSettings()->enableHighlightSound.setValue(value.toBool());
// }
// } break;
// case 3: {
+21
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
+8 -28
View File
@@ -1,42 +1,22 @@
#pragma once
#include <QDebug>
#include "debug/Log.hpp"
#include <QElapsedTimer>
#include <boost/current_function.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 {
class BenchmarkGuard : boost::noncopyable
{
QElapsedTimer timer;
QString name;
public:
BenchmarkGuard(const QString &_name)
: name(_name)
{
timer.start();
}
BenchmarkGuard(const QString &_name);
~BenchmarkGuard();
qreal getElapsedMs();
~BenchmarkGuard()
{
qDebug() << this->name << float(timer.nsecsElapsed()) / 1000000.0f
<< "ms";
}
qreal getElapsedMs()
{
return qreal(timer.nsecsElapsed()) / 1000000.0;
}
private:
QElapsedTimer timer_;
QString name_;
};
} // namespace chatterino
+9 -4
View File
@@ -8,17 +8,22 @@
namespace chatterino {
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")
<< fS(formatString, std::forward<Args>(args)...).c_str();
}
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")
<< fS(formatString, std::forward<Args>(args)...).c_str();
log(std::string(formatString), std::forward<Args>(args)...);
}
template <typename... Args>
inline void log(const QString &formatString, Args &&... args)
{
log(formatString.toStdString(), std::forward<Args>(args)...);
}
} // namespace chatterino
+4 -2
View File
@@ -5,13 +5,15 @@
#include <QApplication>
#include <QStringList>
#include <messages/Image.hpp>
#include <memory>
using namespace chatterino;
int main(int argc, char **argv)
{
auto shared = std::make_shared<QString>();
log(std::atomic_is_lock_free(&shared));
QApplication a(argc, argv);
// convert char** to QStringList
+24 -54
View File
@@ -15,61 +15,31 @@ bool operator!=(const Emote &a, const Emote &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);
//}
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
+5 -27
View File
@@ -7,9 +7,6 @@
#include <memory>
#include <unordered_map>
QStringAlias(EmoteId);
QStringAlias(EmoteName);
namespace chatterino {
struct Emote {
@@ -37,29 +34,10 @@ using EmoteIdMap = std::unordered_map<EmoteId, EmotePtr>;
using WeakEmoteMap = std::unordered_map<EmoteName, std::weak_ptr<const Emote>>;
using WeakEmoteIdMap = std::unordered_map<EmoteId, std::weak_ptr<const Emote>>;
// struct EmoteData2 {
// EmoteName name;
// ImageSet images;
// Tooltip tooltip;
// Url homePage;
//};
//
// class Emote
//{
// public:
// Emote(EmoteData2 &&data);
// Emote(const EmoteData2 &data);
//
// const Url &getHomePage() const;
// const EmoteName &getName() const;
// const Tooltip &getTooltip() const;
// const ImageSet &getImages() const;
// const QString &getCopyString() const;
// bool operator==(const Emote &other) const;
// bool operator!=(const Emote &other) const;
//
// private:
// EmoteData2 data_;
//};
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
-93
View File
@@ -1,93 +0,0 @@
#pragma once
#include <QString>
#include <boost/optional.hpp>
#include <unordered_map>
#include <util/QStringHash.hpp>
#include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp"
namespace chatterino {
template <typename TKey>
class MapReplacement
{
public:
MapReplacement(std::unordered_map<TKey, EmotePtr> &items)
: oldItems_(items)
{
}
void add(const TKey &key, const Emote &data)
{
this->add(key, Emote(data));
}
void add(const TKey &key, Emote &&data)
{
auto it = this->oldItems_.find(key);
if (it != this->oldItems_.end() && *it->second == data) {
this->newItems_[key] = it->second;
} else {
this->newItems_[key] = std::make_shared<Emote>(std::move(data));
}
}
void apply()
{
this->oldItems_ = std::move(this->newItems_);
}
private:
std::unordered_map<TKey, EmotePtr> &oldItems_;
std::unordered_map<TKey, EmotePtr> newItems_;
};
template <typename TKey>
class EmoteCache
{
public:
using Iterator = typename std::unordered_map<TKey, EmotePtr>::iterator;
using ConstIterator = typename std::unordered_map<TKey, EmotePtr>::iterator;
Iterator begin()
{
return this->items_.begin();
}
ConstIterator begin() const
{
return this->items_.begin();
}
Iterator end()
{
return this->items_.end();
}
ConstIterator end() const
{
return this->items_.end();
}
boost::optional<EmotePtr> get(const TKey &key) const
{
auto it = this->items_.find(key);
if (it == this->items_.end())
return boost::none;
else
return it->second;
}
MapReplacement<TKey> makeReplacment()
{
return MapReplacement<TKey>(this->items_);
}
private:
std::unordered_map<TKey, EmotePtr> items_;
};
} // namespace chatterino
-44
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
-20
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
+166 -127
View File
@@ -1,8 +1,10 @@
#include "messages/Image.hpp"
#include "Application.hpp"
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/AssertInGuiThread.hpp"
#include "debug/Benchmark.hpp"
#include "debug/Log.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/WindowManager.hpp"
@@ -15,119 +17,171 @@
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTimer>
#include <functional>
#include <thread>
namespace chatterino {
namespace {
const QPixmap *getPixmap(const Pixmap &pixmap)
{
if (pixmap.which() == 0)
return boost::get<const QPixmap *>(pixmap);
else
return boost::get<std::unique_ptr<QPixmap>>(pixmap).get();
}
// Frames
Frames::Frames()
{
DebugCount::increase("images");
}
// Frames
Frames::Frames()
{
DebugCount::increase("images");
}
Frames::Frames(const QVector<Frame<QPixmap>> &frames)
: items_(frames)
{
assertInGuiThread();
DebugCount::increase("images");
Frames::Frames(std::vector<Frame> &&frames)
: items_(std::move(frames))
{
DebugCount::increase("images");
if (this->animated()) DebugCount::increase("animated images");
}
if (this->animated()) {
DebugCount::increase("animated images");
Frames::~Frames()
{
DebugCount::decrease("images");
if (this->animated()) DebugCount::decrease("animated images");
}
void Frames::advance()
{
this->durationOffset_ += GIF_FRAME_LENGTH;
while (true) {
this->index_ %= this->items_.size();
if (this->durationOffset_ > this->items_[this->index_].duration) {
this->durationOffset_ -= this->items_[this->index_].duration;
this->index_ = (this->index_ + 1) % this->items_.size();
} else {
break;
this->gifTimerConnection_ =
getApp()->emotes->gifTimer.signal.connect(
[this] { this->advance(); });
}
}
}
bool Frames::animated() const
{
return this->items_.size() > 1;
}
Frames::~Frames()
{
assertInGuiThread();
DebugCount::decrease("images");
const QPixmap *Frames::current() const
{
if (this->items_.size() == 0) return nullptr;
return getPixmap(this->items_[this->index_].pixmap);
}
if (this->animated()) {
DebugCount::decrease("animated images");
}
const QPixmap *Frames::first() const
{
if (this->items_.size() == 0) return nullptr;
return getPixmap(this->items_.front().pixmap);
}
this->gifTimerConnection_.disconnect();
}
// functions
std::vector<Frame> readFrames(QImageReader &reader, const Url &url)
{
std::vector<Frame> frames;
void Frames::advance()
{
this->durationOffset_ += GIF_FRAME_LENGTH;
while (true) {
this->index_ %= this->items_.size();
if (this->index_ >= this->items_.size()) {
this->index_ = this->index_;
}
if (this->durationOffset_ > this->items_[this->index_].duration) {
this->durationOffset_ -= this->items_[this->index_].duration;
this->index_ = (this->index_ + 1) % this->items_.size();
} else {
break;
}
}
}
bool Frames::animated() const
{
return this->items_.size() > 1;
}
boost::optional<QPixmap> Frames::current() const
{
if (this->items_.size() == 0) return boost::none;
return this->items_[this->index_].image;
}
boost::optional<QPixmap> Frames::first() const
{
if (this->items_.size() == 0) return boost::none;
return this->items_.front().image;
}
// functions
QVector<Frame<QImage>> readFrames(QImageReader &reader, const Url &url)
{
QVector<Frame<QImage>> frames;
if (reader.imageCount() == 0) {
log("Error while reading image {}: '{}'", url.string,
reader.errorString());
return frames;
}
QImage image;
for (int index = 0; index < reader.imageCount(); ++index) {
if (reader.read(&image)) {
QPixmap::fromImage(image);
int duration = std::max(20, reader.nextImageDelay());
frames.push_back(Frame<QImage>{image, duration});
}
}
if (frames.size() == 0) {
log("Error while reading image {}: '{}'", url.string,
reader.errorString());
}
if (reader.imageCount() <= 0) {
Log("Error while reading image {}: '{}'", url.string,
reader.errorString());
return frames;
}
QImage image;
for (int index = 0; index < reader.imageCount(); ++index) {
if (reader.read(&image)) {
auto pixmap = std::make_unique<QPixmap>(QPixmap::fromImage(image));
// parsed
template <typename Assign>
void assignDelayed(
std::queue<std::pair<Assign, QVector<Frame<QPixmap>>>> &queued,
std::mutex &mutex, std::atomic_bool &loadedEventQueued)
{
std::lock_guard<std::mutex> lock(mutex);
int i = 0;
int duration = std::max(20, reader.nextImageDelay());
frames.push_back(Frame{std::move(pixmap), duration});
while (!queued.empty()) {
queued.front().first(queued.front().second);
queued.pop();
if (++i > 50) {
QTimer::singleShot(3, [&] {
assignDelayed(queued, mutex, loadedEventQueued);
});
return;
}
}
getApp()->windows->forceLayoutChannelViews();
loadedEventQueued = false;
}
if (frames.size() != 0) {
Log("Error while reading image {}: '{}'", url.string,
reader.errorString());
template <typename Assign>
auto makeConvertCallback(const QVector<Frame<QImage>> &parsed,
Assign assign)
{
return [parsed, assign] {
// convert to pixmap
auto frames = QVector<Frame<QPixmap>>();
std::transform(parsed.begin(), parsed.end(),
std::back_inserter(frames), [](auto &frame) {
return Frame<QPixmap>{
QPixmap::fromImage(frame.image),
frame.duration};
});
// put into stack
static std::queue<std::pair<Assign, QVector<Frame<QPixmap>>>>
queued;
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
queued.emplace(assign, frames);
static std::atomic_bool loadedEventQueued{false};
if (!loadedEventQueued) {
loadedEventQueued = true;
QTimer::singleShot(100, [=] {
assignDelayed(queued, mutex, loadedEventQueued);
});
}
};
}
return frames;
}
void queueLoadedEvent()
{
static auto eventQueued = false;
if (!eventQueued) {
eventQueued = true;
QTimer::singleShot(250, [] {
getApp()->windows->incGeneration();
getApp()->windows->layoutChannelViews();
eventQueued = false;
});
}
}
} // namespace
// IMAGE2
std::atomic<bool> Image::loadedEventQueued{false};
ImagePtr Image::fromUrl(const Url &url, qreal scale)
{
static std::unordered_map<Url, std::weak_ptr<Image>> cache;
@@ -140,18 +194,13 @@ ImagePtr Image::fromUrl(const Url &url, qreal scale)
if (!shared) {
cache[url] = shared = ImagePtr(new Image(url, scale));
} else {
Warn("same image loaded multiple times: {}", url.string);
// Warn("same image loaded multiple times: {}", url.string);
}
return shared;
}
ImagePtr Image::fromOwningPixmap(std::unique_ptr<QPixmap> pixmap, qreal scale)
{
return ImagePtr(new Image(std::move(pixmap), scale));
}
ImagePtr Image::fromNonOwningPixmap(QPixmap *pixmap, qreal scale)
ImagePtr Image::fromPixmap(const QPixmap &pixmap, qreal scale)
{
return ImagePtr(new Image(pixmap, scale));
}
@@ -171,23 +220,15 @@ Image::Image(const Url &url, qreal scale)
: url_(url)
, scale_(scale)
, shouldLoad_(true)
, frames_(std::make_unique<Frames>())
{
}
Image::Image(std::unique_ptr<QPixmap> owning, qreal scale)
Image::Image(const QPixmap &pixmap, qreal scale)
: scale_(scale)
, frames_(std::make_unique<Frames>(
QVector<Frame<QPixmap>>{Frame<QPixmap>{pixmap, 1}}))
{
std::vector<Frame> vec;
vec.push_back(Frame{std::move(owning)});
this->frames_ = std::move(vec);
}
Image::Image(QPixmap *nonOwning, qreal scale)
: scale_(scale)
{
std::vector<Frame> vec;
vec.push_back(Frame{nonOwning});
this->frames_ = std::move(vec);
}
const Url &Image::url() const
@@ -195,7 +236,7 @@ const Url &Image::url() const
return this->url_;
}
const QPixmap *Image::pixmap() const
boost::optional<QPixmap> Image::pixmap() const
{
assertInGuiThread();
@@ -204,7 +245,7 @@ const QPixmap *Image::pixmap() const
const_cast<Image *>(this)->load();
}
return this->frames_.current();
return this->frames_->current();
}
qreal Image::scale() const
@@ -212,7 +253,7 @@ qreal Image::scale() const
return this->scale_;
}
bool Image::empty() const
bool Image::isEmpty() const
{
return this->empty_;
}
@@ -221,14 +262,14 @@ bool Image::animated() const
{
assertInGuiThread();
return this->frames_.animated();
return this->frames_->animated();
}
int Image::width() const
{
assertInGuiThread();
if (auto pixmap = this->frames_.first())
if (auto pixmap = this->frames_->first())
return pixmap->width() * this->scale_;
else
return 16;
@@ -238,7 +279,7 @@ int Image::height() const
{
assertInGuiThread();
if (auto pixmap = this->frames_.first())
if (auto pixmap = this->frames_->first())
return pixmap->height() * this->scale_;
else
return 16;
@@ -247,39 +288,37 @@ int Image::height() const
void Image::load()
{
NetworkRequest req(this->url().string);
req.setExecuteConcurrently(true);
req.setCaller(&this->object_);
req.setUseQuickLoadCache(true);
req.onSuccess([this, weak = weakOf(this)](auto result) -> Outcome {
assertInGuiThread();
req.onSuccess([that = this, weak = weakOf(this)](auto result) -> Outcome {
auto shared = weak.lock();
if (!shared) return Failure;
auto data = result.getData();
// const cast since we are only reading from it
QBuffer buffer(const_cast<QByteArray *>(&result.getData()));
QBuffer buffer(const_cast<QByteArray *>(&data));
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer);
auto parsed = readFrames(reader, that->url());
postToThread(makeConvertCallback(parsed, [weak](auto frames) {
if (auto shared = weak.lock())
shared->frames_ = std::make_unique<Frames>(frames);
}));
this->frames_ = readFrames(reader, this->url());
return Success;
});
req.onError([this, weak = weakOf(this)](int) {
auto shared = weak.lock();
if (!shared) return false;
this->frames_ = std::vector<Frame>();
return false;
});
req.execute();
}
bool Image::operator==(const Image &other) const
{
if (this->empty() && other.empty()) return true;
if (this->isEmpty() && other.isEmpty()) 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;
}
+31 -34
View File
@@ -1,43 +1,45 @@
#pragma once
#include "common/Common.hpp"
#include <QPixmap>
#include <QString>
#include <QThread>
#include <QVector>
#include <atomic>
#include <boost/noncopyable.hpp>
#include <boost/optional.hpp>
#include <boost/variant.hpp>
#include <memory>
#include <mutex>
#include <pajlada/signals/signal.hpp>
#include "common/Aliases.hpp"
#include "common/NullablePtr.hpp"
namespace chatterino {
namespace {
using Pixmap = boost::variant<const QPixmap *, std::unique_ptr<QPixmap>>;
struct Frame {
Pixmap pixmap;
int duration;
};
class Frames
{
public:
Frames();
Frames(std::vector<Frame> &&frames);
~Frames();
Frames(Frames &&other) = default;
Frames &operator=(Frames &&other) = default;
template <typename Image>
struct Frame {
Image image;
int duration;
};
class Frames : boost::noncopyable
{
public:
Frames();
Frames(const QVector<Frame<QPixmap>> &frames);
~Frames();
bool animated() const;
void advance();
const QPixmap *current() const;
const QPixmap *first() const;
bool animated() const;
void advance();
boost::optional<QPixmap> current() const;
boost::optional<QPixmap> first() const;
private:
std::vector<Frame> items_;
int index_{0};
int durationOffset_{0};
};
private:
QVector<Frame<QPixmap>> items_;
int index_{0};
int durationOffset_{0};
pajlada::Signals::Connection gifTimerConnection_;
};
} // namespace
class Image;
@@ -47,15 +49,13 @@ class Image : public std::enable_shared_from_this<Image>, boost::noncopyable
{
public:
static ImagePtr fromUrl(const Url &url, qreal scale = 1);
static ImagePtr fromOwningPixmap(std::unique_ptr<QPixmap> pixmap,
qreal scale = 1);
static ImagePtr fromNonOwningPixmap(QPixmap *pixmap, qreal scale = 1);
static ImagePtr fromPixmap(const QPixmap &pixmap, qreal scale = 1);
static ImagePtr getEmpty();
const Url &url() const;
const QPixmap *pixmap() const;
boost::optional<QPixmap> pixmap() const;
qreal scale() const;
bool empty() const;
bool isEmpty() const;
int width() const;
int height() const;
bool animated() const;
@@ -66,8 +66,7 @@ public:
private:
Image();
Image(const Url &url, qreal scale);
Image(std::unique_ptr<QPixmap> owning, qreal scale);
Image(QPixmap *nonOwning, qreal scale);
Image(const QPixmap &nonOwning, qreal scale);
void load();
@@ -75,9 +74,7 @@ private:
qreal scale_{1};
bool empty_{false};
bool shouldLoad_{false};
Frames frames_{};
std::unique_ptr<Frames> frames_{};
QObject object_{};
static std::atomic<bool> loadedEventQueued;
};
} // namespace chatterino
+9 -15
View File
@@ -1,7 +1,5 @@
#include "ImageSet.hpp"
#include "messages/ImageSet.hpp"
#include "Application.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Settings.hpp"
namespace chatterino {
@@ -60,23 +58,19 @@ const ImagePtr &ImageSet::getImage3() const
const ImagePtr &ImageSet::getImage(float scale) const
{
int quality = getSettings()->preferredEmoteQuality;
int quality = 1;
if (!quality) {
if (scale > 3.999)
quality = 3;
else if (scale > 1.999)
quality = 2;
else
scale = 1;
}
if (scale > 2.999)
quality = 3;
else if (scale > 1.5)
quality = 2;
if (!this->imageX3_->empty() && quality == 3) {
if (!this->imageX3_->isEmpty() && quality == 3) {
return this->imageX3_;
}
if (!this->imageX2_->empty() && quality == 2) {
return this->imageX3_;
if (!this->imageX2_->isEmpty() && quality == 2) {
return this->imageX2_;
}
return this->imageX1_;
-2
View File
@@ -21,8 +21,6 @@ public:
const ImagePtr &getImage(float scale) const;
ImagePtr getImage(float scale);
bool operator==(const ImageSet &other) const;
bool operator!=(const ImageSet &other) const;
+1 -1
View File
@@ -1,7 +1,6 @@
#pragma once
#include <QString>
#include <common/Common.hpp>
namespace chatterino {
@@ -14,6 +13,7 @@ public:
UserInfo,
UserTimeout,
UserBan,
UserWhisper,
InsertText,
ShowMessage,
UserAction,
+2 -2
View File
@@ -1,16 +1,16 @@
#pragma once
#include "common/FlagsEnum.hpp"
#include "messages/MessageElement.hpp"
#include "providers/twitch/PubsubActions.hpp"
#include "widgets/helper/ScrollbarHighlight.hpp"
#include <QTime>
#include <boost/noncopyable.hpp>
#include <cinttypes>
#include <memory>
#include <vector>
namespace chatterino {
class MessageElement;
enum class MessageFlag : uint16_t {
None = 0,
+9 -2
View File
@@ -1,6 +1,9 @@
#include "MessageBuilder.hpp"
#include "common/LinkParser.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "providers/twitch/PubsubActions.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Theme.hpp"
@@ -17,7 +20,7 @@ MessagePtr makeSystemMessage(const QString &text)
}
MessageBuilder::MessageBuilder()
: message_(std::make_unique<Message>())
: message_(std::make_shared<Message>())
{
}
@@ -82,6 +85,7 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
}
MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
: MessageBuilder()
{
this->emplace<TimestampElement>();
this->message().flags.set(MessageFlag::System);
@@ -127,6 +131,7 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
}
MessageBuilder::MessageBuilder(const UnbanAction &action)
: MessageBuilder()
{
this->emplace<TimestampElement>();
this->message().flags.set(MessageFlag::System);
@@ -163,7 +168,9 @@ Message &MessageBuilder::message()
MessagePtr MessageBuilder::release()
{
return MessagePtr(this->message_.release());
std::shared_ptr<Message> ptr;
this->message_.swap(ptr);
return ptr;
}
void MessageBuilder::append(std::unique_ptr<MessageElement> element)
+14 -2
View File
@@ -1,11 +1,15 @@
#pragma once
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include <QRegularExpression>
#include <ctime>
namespace chatterino {
struct BanAction;
struct UnbanAction;
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
struct SystemMessageTag {
};
@@ -16,6 +20,14 @@ const TimeoutMessageTag timeoutMessage{};
MessagePtr makeSystemMessage(const QString &text);
struct MessageParseArgs {
bool disablePingSounds = false;
bool isReceivedWhisper = false;
bool isSentWhisper = false;
bool trimSubscriberUsername = false;
bool isStaffOrBroadcaster = false;
};
class MessageBuilder
{
public:
@@ -48,7 +60,7 @@ public:
}
private:
std::unique_ptr<Message> message_;
std::shared_ptr<Message> message_;
};
} // namespace chatterino
+2
View File
@@ -1,5 +1,7 @@
#include "MessageColor.hpp"
#include "singletons/Theme.hpp"
namespace chatterino {
MessageColor::MessageColor(const QColor &color)
+1 -2
View File
@@ -1,10 +1,9 @@
#pragma once
#include "singletons/Theme.hpp"
#include <QColor>
namespace chatterino {
class Theme;
struct MessageColor {
enum Type { Custom, Text, Link, System };
+9
View File
@@ -0,0 +1,9 @@
#include "MessageContainer.hpp"
namespace chatterino {
MessageContainer::MessageContainer()
{
}
} // namespace chatterino
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <deque>
namespace chatterino {
class MessageContainer
{
public:
MessageContainer();
};
} // namespace chatterino
+6 -5
View File
@@ -1,12 +1,13 @@
#include "messages/MessageElement.hpp"
#include "Application.hpp"
#include "common/Emotemap.hpp"
#include "controllers/moderationactions/ModerationActions.hpp"
#include "debug/Benchmark.hpp"
#include "messages/Emote.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp"
#include "messages/layouts/MessageLayoutElement.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "util/DebugCount.hpp"
namespace chatterino {
@@ -102,7 +103,7 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
if (flags.hasAny(this->getFlags())) {
if (flags.has(MessageElementFlag::EmoteImages)) {
auto image = this->emote_->images.getImage(container.getScale());
if (image->empty()) return;
if (image->isEmpty()) return;
auto size = QSize(int(container.getScale() * image->width()),
int(container.getScale() * image->height()));
@@ -224,8 +225,8 @@ void TimestampElement::addToContainer(MessageLayoutContainer &container,
{
if (flags.hasAny(this->getFlags())) {
auto app = getApp();
if (app->settings->timestampFormat != this->format_) {
this->format_ = app->settings->timestampFormat.getValue();
if (getSettings()->timestampFormat != this->format_) {
this->format_ = getSettings()->timestampFormat.getValue();
this->element_.reset(this->formatTime(this->time_));
}
@@ -237,7 +238,7 @@ TextElement *TimestampElement::formatTime(const QTime &time)
{
static QLocale locale("en_US");
QString format = locale.toString(time, getApp()->settings->timestampFormat);
QString format = locale.toString(time, getSettings()->timestampFormat);
return new TextElement(format, MessageElementFlag::Timestamp,
MessageColor::System, FontStyle::ChatMedium);
+7 -4
View File
@@ -1,9 +1,6 @@
#pragma once
#include "common/Emotemap.hpp"
#include "common/FlagsEnum.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "messages/Link.hpp"
#include "messages/MessageColor.hpp"
#include "singletons/Fonts.hpp"
@@ -12,14 +9,20 @@
#include <QString>
#include <QTime>
#include <boost/noncopyable.hpp>
#include <cstdint>
#include <memory>
#include <vector>
namespace chatterino {
class Channel;
struct MessageLayoutContainer;
class Image;
using ImagePtr = std::shared_ptr<Image>;
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
enum class MessageElementFlag {
None = 0,
Misc = (1 << 0),
-8
View File
@@ -2,12 +2,4 @@
namespace chatterino {
struct MessageParseArgs {
bool disablePingSounds = false;
bool isReceivedWhisper = false;
bool isSentWhisper = false;
bool trimSubscriberUsername = false;
bool isStaffOrBroadcaster = false;
};
} // namespace chatterino
+3 -8
View File
@@ -1,5 +1,6 @@
#pragma once
#include <tuple>
#include <utility>
namespace chatterino {
@@ -23,14 +24,8 @@ struct SelectionItem {
bool operator<(const SelectionItem &b) const
{
if (this->messageIndex < b.messageIndex) {
return true;
}
if (this->messageIndex == b.messageIndex &&
this->charIndex < b.charIndex) {
return true;
}
return false;
return std::tie(this->messageIndex, this->charIndex) <
std::tie(b.messageIndex, b.charIndex);
}
bool operator>(const SelectionItem &b) const
+33 -26
View File
@@ -2,8 +2,12 @@
#include "Application.hpp"
#include "debug/Benchmark.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp"
#include "util/DebugCount.hpp"
@@ -24,6 +28,7 @@ namespace chatterino {
MessageLayout::MessageLayout(MessagePtr message)
: message_(message)
, buffer_(nullptr)
, container_(std::make_shared<MessageLayoutContainer>())
{
DebugCount::increase("message layout");
}
@@ -41,7 +46,7 @@ const Message *MessageLayout::getMessage()
// Height
int MessageLayout::getHeight() const
{
return container_.getHeight();
return container_->getHeight();
}
// Layout
@@ -68,7 +73,7 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
// check if work mask changed
layoutRequired |= this->currentWordFlags_ != flags;
this->currentWordFlags_ = flags; // app->settings->getWordTypeMask();
this->currentWordFlags_ = flags; // getSettings()->getWordTypeMask();
// check if layout was requested manually
layoutRequired |= this->flags.has(MessageLayoutFlag::RequiresLayout);
@@ -82,9 +87,9 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
return false;
}
int oldHeight = this->container_.getHeight();
int oldHeight = this->container_->getHeight();
this->actuallyLayout(width, flags);
if (widthChanged || this->container_.getHeight() != oldHeight) {
if (widthChanged || this->container_->getHeight() != oldHeight) {
this->deleteBuffer();
}
this->invalidateBuffer();
@@ -103,22 +108,22 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags _flags)
messageFlags.unset(MessageFlag::Collapsed);
}
this->container_.begin(width, this->scale_, messageFlags);
this->container_->begin(width, this->scale_, messageFlags);
for (const auto &element : this->message_->elements) {
element->addToContainer(this->container_, _flags);
element->addToContainer(*this->container_, _flags);
}
if (this->height_ != this->container_.getHeight()) {
if (this->height_ != this->container_->getHeight()) {
this->deleteBuffer();
}
this->container_.end();
this->height_ = this->container_.getHeight();
this->container_->end();
this->height_ = this->container_->getHeight();
// collapsed state
this->flags.unset(MessageLayoutFlag::Collapsed);
if (this->container_.isCollapsed()) {
if (this->container_->isCollapsed()) {
this->flags.set(MessageLayoutFlag::Collapsed);
}
}
@@ -135,11 +140,12 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
if (!pixmap) {
#ifdef Q_OS_MACOS
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
int(container_.getHeight() *
int(container_->getHeight() *
painter.device()->devicePixelRatioF()));
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
#else
pixmap = new QPixmap(width, std::max(16, this->container_.getHeight()));
pixmap =
new QPixmap(width, std::max(16, this->container_->getHeight()));
#endif
this->buffer_ = std::shared_ptr<QPixmap>(pixmap);
@@ -157,7 +163,7 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
// this->container.getHeight(), *pixmap);
// draw gif emotes
this->container_.paintAnimatedElements(painter, y);
this->container_->paintAnimatedElements(painter, y);
// draw disabled
if (this->message_->flags.has(MessageFlag::Disabled)) {
@@ -167,12 +173,12 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
// draw selection
if (!selection.isEmpty()) {
this->container_.paintSelection(painter, messageIndex, selection, y);
this->container_->paintSelection(painter, messageIndex, selection, y);
}
// draw message seperation line
if (app->settings->separateMessages.getValue()) {
painter.fillRect(0, y, this->container_.getWidth(), 1,
if (getSettings()->separateMessages.getValue()) {
painter.fillRect(0, y, this->container_->getWidth(), 1,
app->themes->splits.messageSeperator);
}
@@ -184,9 +190,9 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
: app->themes->tabs.selected.backgrounds.unfocused.color();
QBrush brush(color, static_cast<Qt::BrushStyle>(
app->settings->lastMessagePattern.getValue()));
getSettings()->lastMessagePattern.getValue()));
painter.fillRect(0, y + this->container_.getHeight() - 1,
painter.fillRect(0, y + this->container_->getHeight() - 1,
pixmap->width(), 1, brush);
}
@@ -208,7 +214,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
backgroundColor = app->themes->messages.backgrounds.highlighted;
} else if (this->message_->flags.has(MessageFlag::Subscription)) {
backgroundColor = app->themes->messages.backgrounds.subscription;
} else if (app->settings->alternateMessageBackground.getValue() &&
} else if (getSettings()->alternateMessageBackground.getValue() &&
this->flags.has(MessageLayoutFlag::AlternateBackground)) {
backgroundColor = app->themes->messages.backgrounds.alternate;
} else {
@@ -217,7 +223,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
painter.fillRect(buffer->rect(), backgroundColor);
// draw message
this->container_.paintElements(painter);
this->container_->paintElements(painter);
#ifdef FOURTF
// debug
@@ -252,7 +258,7 @@ void MessageLayout::deleteCache()
this->deleteBuffer();
#ifdef XD
this->container_.clear();
this->container_->clear();
#endif
}
@@ -265,22 +271,23 @@ void MessageLayout::deleteCache()
const MessageLayoutElement *MessageLayout::getElementAt(QPoint point)
{
// go through all words and return the first one that contains the point.
return this->container_.getElementAt(point);
return this->container_->getElementAt(point);
}
int MessageLayout::getLastCharacterIndex() const
{
return this->container_.getLastCharacterIndex();
return this->container_->getLastCharacterIndex();
}
int MessageLayout::getSelectionIndex(QPoint position)
{
return this->container_.getSelectionIndex(position);
return this->container_->getSelectionIndex(position);
}
void MessageLayout::addSelectionText(QString &str, int from, int to)
void MessageLayout::addSelectionText(QString &str, int from, int to,
CopyMode copymode)
{
this->container_.addSelectionText(str, from, to);
this->container_->addSelectionText(str, from, to, copymode);
}
} // namespace chatterino
+14 -10
View File
@@ -1,19 +1,25 @@
#pragma once
#include "common/Common.hpp"
#include "common/FlagsEnum.hpp"
#include "messages/Message.hpp"
#include "messages/Selection.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp"
#include "messages/layouts/MessageLayoutElement.hpp"
#include <QPixmap>
#include <boost/noncopyable.hpp>
#include <cinttypes>
#include <memory>
namespace chatterino {
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
struct Selection;
struct MessageLayoutContainer;
class MessageLayoutElement;
enum class MessageElementFlag;
using MessageElementFlags = FlagsEnum<MessageElementFlag>;
enum class MessageLayoutFlag : uint8_t {
RequiresBufferUpdate = 1 << 1,
RequiresLayout = 1 << 2,
@@ -31,13 +37,10 @@ public:
const Message *getMessage();
// Height
int getHeight() const;
// Flags
MessageLayoutFlags flags;
// Layout
bool layout(int width, float scale_, MessageElementFlags flags);
// Painting
@@ -52,7 +55,8 @@ public:
const MessageLayoutElement *getElementAt(QPoint point);
int getLastCharacterIndex() const;
int getSelectionIndex(QPoint position);
void addSelectionText(QString &str, int from = 0, int to = INT_MAX);
void addSelectionText(QString &str, int from = 0, int to = INT_MAX,
CopyMode copymode = CopyMode::Everything);
// Misc
bool isDisabled() const;
@@ -60,7 +64,7 @@ public:
private:
// variables
MessagePtr message_;
MessageLayoutContainer container_;
std::shared_ptr<MessageLayoutContainer> container_;
std::shared_ptr<QPixmap> buffer_ = nullptr;
bool bufferValid_ = false;
+29 -16
View File
@@ -1,16 +1,20 @@
#include "MessageLayoutContainer.hpp"
#include "Application.hpp"
#include "MessageLayoutElement.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "messages/Selection.hpp"
#include "messages/layouts/MessageLayoutElement.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include <QDebug>
#include <QPainter>
#define COMPACT_EMOTES_OFFSET 6
#define MAX_UNCOLLAPSED_LINES \
(getApp()->settings->collpseMessagesMinLines.getValue())
(getSettings()->collpseMessagesMinLines.getValue())
namespace chatterino {
@@ -126,9 +130,10 @@ void MessageLayoutContainer::breakLine()
int xOffset = 0;
if (this->flags_.has(MessageFlag::Centered) && this->elements_.size() > 0) {
xOffset = (width_ - this->elements_.at(this->elements_.size() - 1)
->getRect()
.right()) /
xOffset = (width_ - this->elements_.at(0)->getRect().left() -
this->elements_.at(this->elements_.size() - 1)
->getRect()
.right()) /
2;
}
@@ -230,7 +235,7 @@ void MessageLayoutContainer::end()
bool MessageLayoutContainer::canCollapse()
{
return getApp()->settings->collpseMessagesMinLines.getValue() > 0 &&
return getSettings()->collpseMessagesMinLines.getValue() > 0 &&
this->flags_.has(MessageFlag::Collapsed);
}
@@ -500,33 +505,41 @@ int MessageLayoutContainer::getLastCharacterIndex() const
return this->lines_.back().endCharIndex;
}
void MessageLayoutContainer::addSelectionText(QString &str, int from, int to)
void MessageLayoutContainer::addSelectionText(QString &str, int from, int to,
CopyMode copymode)
{
int index = 0;
bool first = true;
for (std::unique_ptr<MessageLayoutElement> &ele : this->elements_) {
int c = ele->getSelectionIndexCount();
for (auto &element : this->elements_) {
if (copymode == CopyMode::OnlyTextAndEmotes) {
if (element->getCreator().getFlags().hasAny(
{MessageElementFlag::Timestamp,
MessageElementFlag::Username, MessageElementFlag::Badges}))
continue;
}
auto indexCount = element->getSelectionIndexCount();
if (first) {
if (index + c > from) {
ele->addCopyTextToString(str, from - index, to - index);
if (index + indexCount > from) {
element->addCopyTextToString(str, from - index, to - index);
first = false;
if (index + c > to) {
if (index + indexCount > to) {
break;
}
}
} else {
if (index + c > to) {
ele->addCopyTextToString(str, 0, to - index);
if (index + indexCount > to) {
element->addCopyTextToString(str, 0, to - index);
break;
} else {
ele->addCopyTextToString(str);
element->addCopyTextToString(str);
}
}
index += c;
index += indexCount;
}
}
@@ -1,18 +1,21 @@
#pragma once
#include <memory>
#include <vector>
#include <QPoint>
#include <QRect>
#include <memory>
#include <vector>
#include "messages/Message.hpp"
#include "common/Common.hpp"
#include "common/FlagsEnum.hpp"
#include "messages/Selection.hpp"
#include "messages/layouts/MessageLayoutElement.hpp"
class QPainter;
namespace chatterino {
class MessageLayoutElement;
enum class MessageFlag : uint16_t;
using MessageFlags = FlagsEnum<MessageFlag>;
struct Margin {
int top;
@@ -72,7 +75,7 @@ struct MessageLayoutContainer {
// selection
int getSelectionIndex(QPoint point);
int getLastCharacterIndex() const;
void addSelectionText(QString &str, int from, int to);
void addSelectionText(QString &str, int from, int to, CopyMode copymode);
bool isCollapsed();
@@ -92,7 +95,7 @@ private:
// variables
float scale_ = 1.f;
int width_ = 0;
MessageFlags flags_ = MessageFlag::None;
MessageFlags flags_{};
int line_ = 0;
int height_ = 0;
int currentX_ = 0;
@@ -1,7 +1,10 @@
#include "messages/layouts/MessageLayoutElement.hpp"
#include "Application.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "messages/MessageElement.hpp"
#include "singletons/Theme.hpp"
#include "util/DebugCount.hpp"
#include <QDebug>
@@ -75,11 +78,12 @@ ImageLayoutElement::ImageLayoutElement(MessageElement &creator, ImagePtr image,
void ImageLayoutElement::addCopyTextToString(QString &str, int from,
int to) const
{
// str += this->image_->getCopyString();
str += "not implemented";
if (this->hasTrailingSpace()) {
str += " ";
const auto *emoteElement = dynamic_cast<EmoteElement *>(&this->getCreator());
if (emoteElement) {
str += emoteElement->getEmote()->getCopyString();
if (this->hasTrailingSpace()) {
str += " ";
}
}
}
@@ -3,19 +3,19 @@
#include <QPoint>
#include <QRect>
#include <QString>
#include <boost/noncopyable.hpp>
#include <climits>
#include "messages/Image.hpp"
#include "messages/Link.hpp"
#include "messages/MessageColor.hpp"
#include "singletons/Fonts.hpp"
class QPainter;
namespace chatterino {
class MessageElement;
class Image;
using ImagePtr = std::shared_ptr<Image>;
enum class FontStyle : uint8_t;
class MessageLayoutElement : boost::noncopyable
{
+134 -92
View File
@@ -1,7 +1,9 @@
#include "providers/bttv/BttvEmotes.hpp"
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "messages/ImageSet.hpp"
#include "providers/twitch/TwitchChannel.hpp"
@@ -10,11 +12,140 @@
#include <QThread>
namespace chatterino {
namespace {
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{
urlTemplate.detach();
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
return {urlTemplate.replace("{{id}}", id.string)
.replace("{{image}}", emoteScale)};
}
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes)
{
auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate =
qS("https:") + jsonRoot.value("urlTemplate").toString();
for (auto jsonEmote : jsonEmotes) {
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
auto name =
EmoteName{jsonEmote.toObject().value("code").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 />Global Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
emotes[name] =
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
}
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
//
// BttvEmotes
//
BttvEmotes::BttvEmotes()
: global_(std::make_shared<EmoteMap>())
{
}
std::shared_ptr<const EmoteMap> BttvEmotes::emotes() const
{
return this->global_.get();
}
boost::optional<EmotePtr> BttvEmotes::emote(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::loadEmotes()
{
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();
}
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();
@@ -22,93 +153,4 @@ Url getEmoteLink(QString urlTemplate, const EmoteId &id,
.replace("{{image}}", emoteScale)};
}
} // namespace
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 jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate =
QString("https:" + jsonRoot.value("urlTemplate").toString());
for (const QJsonValue &jsonEmote : jsonEmotes) {
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
auto name = EmoteName{jsonEmote.toObject().value("code").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 />Global Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
auto it = currentEmotes.find(name);
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)};
}
} // namespace chatterino
+17 -16
View File
@@ -1,33 +1,34 @@
#pragma once
#include <memory>
#include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp"
#include "messages/EmoteCache.hpp"
#include "boost/optional.hpp"
#include "common/Aliases.hpp"
#include "common/Atomic.hpp"
namespace chatterino {
class BttvEmotes final : std::enable_shared_from_this<BttvEmotes>
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class EmoteMap;
class BttvEmotes final
{
static constexpr const char *globalEmoteApiUrl =
"https://api.betterttv.net/2/emotes";
static constexpr const char *bttvChannelEmoteApiUrl =
"https://api.betterttv.net/2/channels/";
public:
// BttvEmotes();
BttvEmotes();
AccessGuard<const EmoteMap> accessGlobalEmotes() const;
boost::optional<EmotePtr> getGlobalEmote(const EmoteName &name);
boost::optional<EmotePtr> getEmote(const EmoteId &id);
void loadGlobalEmotes();
std::shared_ptr<const EmoteMap> emotes() const;
boost::optional<EmotePtr> emote(const EmoteName &name) const;
void loadEmotes();
static void loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback);
private:
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes);
UniqueAccess<EmoteMap> globalEmotes_;
// UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;
Atomic<std::shared_ptr<const EmoteMap>> global_;
};
} // namespace chatterino
@@ -11,78 +11,4 @@
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
@@ -6,11 +6,4 @@ class QString;
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
+24 -22
View File
@@ -14,39 +14,41 @@ ChatterinoBadges::ChatterinoBadges()
boost::optional<EmotePtr> ChatterinoBadges::getBadge(const UserName &username)
{
return this->badges.access()->get(username);
return boost::none;
// return this->badges.access()->get(username);
}
void ChatterinoBadges::loadChatterinoBadges()
{
static QString url("https://fourtf.com/chatterino/badges.json");
// static QString url("https://fourtf.com/chatterino/badges.json");
NetworkRequest req(url);
req.setCaller(QThread::currentThread());
// NetworkRequest req(url);
// req.setCaller(QThread::currentThread());
req.onSuccess([this](auto result) {
auto jsonRoot = result.parseJson();
auto badges = this->badges.access();
auto replacement = badges->makeReplacment();
// req.onSuccess([this](auto result) {
// auto jsonRoot = result.parseJson();
// auto badges = this->badges.access();
// auto replacement = badges->makeReplacment();
for (auto jsonBadge_ : jsonRoot.value("badges").toArray()) {
auto jsonBadge = jsonBadge_.toObject();
// for (auto jsonBadge_ : jsonRoot.value("badges").toArray()) {
// auto jsonBadge = jsonBadge_.toObject();
auto emote = Emote{
EmoteName{}, ImageSet{Url{jsonBadge.value("image").toString()}},
Tooltip{jsonBadge.value("tooltip").toString()}, Url{}};
// auto emote = Emote{
// EmoteName{},
// ImageSet{Url{jsonBadge.value("image").toString()}},
// Tooltip{jsonBadge.value("tooltip").toString()}, Url{}};
for (auto jsonUser : jsonBadge.value("users").toArray()) {
replacement.add(UserName{jsonUser.toString()},
std::move(emote));
}
}
// for (auto jsonUser : jsonBadge.value("users").toArray()) {
// replacement.add(UserName{jsonUser.toString()},
// std::move(emote));
// }
// }
replacement.apply();
return Success;
});
// replacement.apply();
// return Success;
//});
req.execute();
// req.execute();
}
} // namespace chatterino
@@ -1,14 +1,14 @@
#pragma once
#include <boost/optional.hpp>
#include <unordered_map>
#include "common/Common.hpp"
#include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp"
#include "messages/EmoteCache.hpp"
#include "common/Aliases.hpp"
namespace chatterino {
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class ChatterinoBadges
{
public:
@@ -19,7 +19,7 @@ public:
private:
void loadChatterinoBadges();
UniqueAccess<EmoteCache<UserName>> badges;
// UniqueAccess<EmoteCache<UserName>> badges;
};
} // namespace chatterino
+75 -92
View File
@@ -2,6 +2,7 @@
#include "Application.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "singletons/Settings.hpp"
#include <rapidjson/error/en.h>
@@ -12,82 +13,81 @@
#include <memory>
namespace chatterino {
namespace {
void parseEmoji(const std::shared_ptr<EmojiData> &emojiData,
const rapidjson::Value &unparsedEmoji,
QString shortCode = QString())
{
static uint unicodeBytes[4];
void parseEmoji(const std::shared_ptr<EmojiData> &emojiData,
const rapidjson::Value &unparsedEmoji,
QString shortCode = QString())
{
static uint unicodeBytes[4];
struct {
bool apple;
bool google;
bool twitter;
bool emojione;
bool facebook;
bool messenger;
} capabilities;
struct {
bool apple;
bool google;
bool twitter;
bool emojione;
bool facebook;
bool messenger;
} capabilities;
if (!shortCode.isEmpty()) {
emojiData->shortCodes.push_back(shortCode);
} else {
const auto &shortCodes = unparsedEmoji["short_names"];
for (const auto &shortCode : shortCodes.GetArray()) {
emojiData->shortCodes.emplace_back(shortCode.GetString());
if (!shortCode.isEmpty()) {
emojiData->shortCodes.push_back(shortCode);
} else {
const auto &shortCodes = unparsedEmoji["short_names"];
for (const auto &shortCode : shortCodes.GetArray()) {
emojiData->shortCodes.emplace_back(shortCode.GetString());
}
}
}
rj::getSafe(unparsedEmoji, "non_qualified", emojiData->nonQualifiedCode);
rj::getSafe(unparsedEmoji, "unified", emojiData->unifiedCode);
rj::getSafe(unparsedEmoji, "non_qualified",
emojiData->nonQualifiedCode);
rj::getSafe(unparsedEmoji, "unified", emojiData->unifiedCode);
rj::getSafe(unparsedEmoji, "has_img_apple", capabilities.apple);
rj::getSafe(unparsedEmoji, "has_img_google", capabilities.google);
rj::getSafe(unparsedEmoji, "has_img_twitter", capabilities.twitter);
rj::getSafe(unparsedEmoji, "has_img_emojione", capabilities.emojione);
rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook);
rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger);
rj::getSafe(unparsedEmoji, "has_img_apple", capabilities.apple);
rj::getSafe(unparsedEmoji, "has_img_google", capabilities.google);
rj::getSafe(unparsedEmoji, "has_img_twitter", capabilities.twitter);
rj::getSafe(unparsedEmoji, "has_img_emojione", capabilities.emojione);
rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook);
rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger);
if (capabilities.apple) {
emojiData->capabilities.insert("Apple");
}
if (capabilities.google) {
emojiData->capabilities.insert("Google");
}
if (capabilities.twitter) {
emojiData->capabilities.insert("Twitter");
}
if (capabilities.emojione) {
emojiData->capabilities.insert("EmojiOne 3");
}
if (capabilities.facebook) {
emojiData->capabilities.insert("Facebook");
}
if (capabilities.messenger) {
emojiData->capabilities.insert("Messenger");
}
if (capabilities.apple) {
emojiData->capabilities.insert("Apple");
}
if (capabilities.google) {
emojiData->capabilities.insert("Google");
}
if (capabilities.twitter) {
emojiData->capabilities.insert("Twitter");
}
if (capabilities.emojione) {
emojiData->capabilities.insert("EmojiOne 3");
}
if (capabilities.facebook) {
emojiData->capabilities.insert("Facebook");
}
if (capabilities.messenger) {
emojiData->capabilities.insert("Messenger");
}
QStringList unicodeCharacters;
if (!emojiData->nonQualifiedCode.isEmpty()) {
unicodeCharacters = emojiData->nonQualifiedCode.toLower().split('-');
} else {
unicodeCharacters = emojiData->unifiedCode.toLower().split('-');
QStringList unicodeCharacters;
if (!emojiData->nonQualifiedCode.isEmpty()) {
unicodeCharacters =
emojiData->nonQualifiedCode.toLower().split('-');
} else {
unicodeCharacters = emojiData->unifiedCode.toLower().split('-');
}
if (unicodeCharacters.length() < 1) {
return;
}
int numUnicodeBytes = 0;
for (const QString &unicodeCharacter : unicodeCharacters) {
unicodeBytes[numUnicodeBytes++] =
QString(unicodeCharacter).toUInt(nullptr, 16);
}
emojiData->value = QString::fromUcs4(unicodeBytes, numUnicodeBytes);
}
if (unicodeCharacters.length() < 1) {
return;
}
int numUnicodeBytes = 0;
for (const QString &unicodeCharacter : unicodeCharacters) {
unicodeBytes[numUnicodeBytes++] =
QString(unicodeCharacter).toUInt(nullptr, 16);
}
emojiData->value = QString::fromUcs4(unicodeBytes, numUnicodeBytes);
}
} // namespace
void Emojis::load()
@@ -103,12 +103,10 @@ void Emojis::load()
void Emojis::loadEmojis()
{
std::map<std::string, QString> toneNames;
toneNames["1F3FB"] = "tone1";
toneNames["1F3FC"] = "tone2";
toneNames["1F3FD"] = "tone3";
toneNames["1F3FE"] = "tone4";
toneNames["1F3FF"] = "tone5";
auto toneNames = std::map<std::string, QString>{
{"1F3FB", "tone1"}, {"1F3FC", "tone2"}, {"1F3FD", "tone3"},
{"1F3FE", "tone4"}, {"1F3FF", "tone5"},
};
QFile file(":/emoji.json");
file.open(QFile::ReadOnly);
@@ -118,7 +116,7 @@ void Emojis::loadEmojis()
rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length());
if (result.Code() != rapidjson::kParseErrorNone) {
Log("JSON parse error: {} ({})",
log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset());
return;
}
@@ -146,7 +144,7 @@ void Emojis::loadEmojis()
auto toneNameIt = toneNames.find(tone);
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);
continue;
}
@@ -218,8 +216,8 @@ void Emojis::loadEmojiSet()
{
auto app = getApp();
app->settings->emojiSet.connect([=](const auto &emojiSet, auto) {
Log("Using emoji set {}", emojiSet);
getSettings()->emojiSet.connect([=](const auto &emojiSet, auto) {
log("Using emoji set {}", emojiSet);
this->emojis.each([=](const auto &name,
std::shared_ptr<EmojiData> &emoji) {
QString emojiSetToUse = emojiSet;
@@ -233,27 +231,12 @@ void Emojis::loadEmojiSet()
// {"Google", "https://cdn.jsdelivr.net/npm/emoji-datasource-google@4.0.4/img/google/64/"},
// {"Messenger", "https://cdn.jsdelivr.net/npm/emoji-datasource-messenger@4.0.4/img/messenger/64/"},
// {"EmojiOne 2", "https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/"},
// {"EmojiOne 3", "https://braize.pajlada.com/emoji/img/emojione/64/"},
// {"Twitter", "https://braize.pajlada.com/emoji/img/twitter/64/"},
// {"Facebook", "https://braize.pajlada.com/emoji/img/facebook/64/"},
// {"Apple", "https://braize.pajlada.com/emoji/img/apple/64/"},
// {"Google", "https://braize.pajlada.com/emoji/img/google/64/"},
// {"Messenger", "https://braize.pajlada.com/emoji/img/messenger/64/"},
{"EmojiOne 3", "https://pajbot.com/static/emoji/img/emojione/64/"},
{"Twitter", "https://pajbot.com/static/emoji/img/twitter/64/"},
{"Facebook", "https://pajbot.com/static/emoji/img/facebook/64/"},
{"Apple", "https://pajbot.com/static/emoji/img/apple/64/"},
{"Google", "https://pajbot.com/static/emoji/img/google/64/"},
{"Messenger", "https://pajbot.com/static/emoji/img/messenger/64/"},
// {"EmojiOne 3", "https://cdn.fourtf.com/emoji/emojione/64/"},
// {"Twitter", "https://cdn.fourtf.com/emoji/twitter/64/"},
// {"Facebook", "https://cdn.fourtf.com/emoji/facebook/64/"},
// {"Apple", "https://cdn.fourtf.com/emoji/apple/64/"},
// {"Google", "https://cdn.fourtf.com/emoji/google/64/"},
// {"Messenger", "https://cdn.fourtf.com/emoji/messenger/64/"},
};
// clang-format on
+3 -3
View File
@@ -1,8 +1,5 @@
#pragma once
#include "common/Emotemap.hpp"
#include "common/SimpleSignalVector.hpp"
#include "messages/Emote.hpp"
#include "util/ConcurrentMap.hpp"
#include <QMap>
@@ -14,6 +11,9 @@
namespace chatterino {
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
struct EmojiData {
// actual byte-representation of the emoji (i.e. \154075\156150 which is
// :male:)
+122 -130
View File
@@ -3,172 +3,164 @@
#include <QJsonArray>
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
namespace chatterino {
namespace {
Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
{
auto emote = urls.value(emoteScale);
if (emote.isUndefined()) {
return {""};
Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
{
auto emote = urls.value(emoteScale);
if (emote.isUndefined()) {
return {""};
}
assert(emote.isString());
return {"https:" + emote.toString()};
}
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
const QString &tooltip, Emote &emoteData)
{
auto url1x = getEmoteLink(urls, "1");
auto url2x = getEmoteLink(urls, "2");
auto url3x = getEmoteLink(urls, "4");
assert(emote.isString());
//, code, tooltip
emoteData.name = name;
emoteData.images =
ImageSet{Image::fromUrl(url1x, 1), Image::fromUrl(url2x, 0.5),
Image::fromUrl(url3x, 0.25)};
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 {"https:" + emote.toString()};
}
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
}
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes)
{
auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = EmoteMap();
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
const QString &tooltip, Emote &emoteData)
{
auto url1x = getEmoteLink(urls, "1");
auto url2x = getEmoteLink(urls, "2");
auto url3x = getEmoteLink(urls, "4");
for (auto jsonSet : jsonSets) {
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
//, code, tooltip
emoteData.name = name;
emoteData.images =
ImageSet{Image::fromUrl(url1x, 1), Image::fromUrl(url2x, 0.5),
Image::fromUrl(url3x, 0.25)};
emoteData.tooltip = {tooltip};
}
for (auto jsonEmoteValue : jsonEmotes) {
auto jsonEmote = jsonEmoteValue.toObject();
auto name = EmoteName{jsonEmote.value("name").toString()};
auto id = EmoteId{jsonEmote.value("id").toString()};
auto urls = jsonEmote.value("urls").toObject();
auto emote = Emote();
fillInEmoteData(urls, name,
name.string + "<br/>Global FFZ Emote", emote);
emote.homePage =
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
emotes[name] =
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
}
}
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
AccessGuard<const EmoteCache<EmoteName>> FfzEmotes::accessGlobalEmotes() const
FfzEmotes::FfzEmotes()
: global_(std::make_shared<EmoteMap>())
{
return this->globalEmotes_.accessConst();
}
boost::optional<EmotePtr> FfzEmotes::getEmote(const EmoteId &id)
std::shared_ptr<const EmoteMap> FfzEmotes::emotes() const
{
auto cache = this->channelEmoteCache_.access();
auto it = cache->find(id);
if (it != cache->end()) {
auto shared = it->second.lock();
if (shared) {
return shared;
}
}
return this->global_.get();
}
boost::optional<EmotePtr> FfzEmotes::emote(const EmoteName &name) const
{
auto emotes = this->global_.get();
auto it = emotes->find(name);
if (it != emotes->end()) return it->second;
return boost::none;
}
boost::optional<EmotePtr> FfzEmotes::getGlobalEmote(const EmoteName &name)
{
return this->globalEmotes_.access()->get(name);
}
void FfzEmotes::loadGlobalEmotes()
void FfzEmotes::loadEmotes()
{
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());
auto emotes = this->emotes();
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();
}
Outcome FfzEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot)
void FfzEmotes::loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{
auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = this->globalEmotes_.access();
auto replacement = emotes->makeReplacment();
log("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n", channelName);
for (auto jsonSet : jsonSets) {
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
NetworkRequest request("https://api.frankerfacez.com/v1/room/" +
channelName);
request.setCaller(QThread::currentThread());
request.setTimeout(3000);
for (auto jsonEmoteValue : jsonEmotes) {
auto jsonEmote = jsonEmoteValue.toObject();
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;
});
auto name = EmoteName{jsonEmote.value("name").toString()};
auto id = EmoteId{jsonEmote.value("id").toString()};
auto urls = jsonEmote.value("urls").toObject();
auto emote = Emote();
fillInEmoteData(urls, name, name.string + "<br/>Global FFZ Emote",
emote);
emote.homePage =
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
replacement.add(name, emote);
}
}
return Success;
}
void FfzEmotes::loadChannelEmotes(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{
// printf("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n",
// qPrintable(channelName));
// QString url("https://api.frankerfacez.com/v1/room/" + channelName);
// NetworkRequest request(url);
// request.setCaller(QThread::currentThread());
// request.setTimeout(3000);
// request.onSuccess([this, channelName, _map](auto result) -> Outcome {
// return this->parseChannelEmotes(result.parseJson());
//});
// 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;
request.execute();
}
} // namespace chatterino
+16 -23
View File
@@ -1,39 +1,32 @@
#pragma once
#include <memory>
#include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp"
#include "messages/EmoteCache.hpp"
#include "boost/optional.hpp"
#include "common/Aliases.hpp"
#include "common/Atomic.hpp"
namespace chatterino {
class FfzEmotes final : std::enable_shared_from_this<FfzEmotes>
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class EmoteMap;
class FfzEmotes final
{
static constexpr const char *globalEmoteApiUrl =
"https://api.frankerfacez.com/v1/set/global";
static constexpr const char *channelEmoteApiUrl =
"https://api.betterttv.net/2/channels/";
public:
// FfzEmotes();
FfzEmotes();
static std::shared_ptr<FfzEmotes> create();
std::shared_ptr<const EmoteMap> emotes() const;
boost::optional<EmotePtr> emote(const EmoteName &name) const;
void loadEmotes();
static void loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback);
AccessGuard<const EmoteCache<EmoteName>> accessGlobalEmotes() const;
boost::optional<EmotePtr> getGlobalEmote(const EmoteName &name);
boost::optional<EmotePtr> getEmote(const EmoteId &id);
void loadGlobalEmotes();
void loadChannelEmotes(const QString &channelName,
std::function<void(EmoteMap &&)> callback);
protected:
Outcome parseGlobalEmotes(const QJsonObject &jsonRoot);
Outcome parseChannelEmotes(const QJsonObject &jsonRoot);
UniqueAccess<EmoteCache<EmoteName>> globalEmotes_;
UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;
private:
Atomic<std::shared_ptr<const EmoteMap>> global_;
};
} // namespace chatterino
+3 -1
View File
@@ -1,6 +1,8 @@
#include "AbstractIrcServer.hpp"
#include "common/Channel.hpp"
#include "common/Common.hpp"
#include "debug/Log.hpp"
#include "messages/LimitedQueueSnapshot.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
@@ -131,7 +133,7 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
chan->destroyed.connect([this, clojuresInCppAreShit] {
// fourtf: issues when the server itself is destroyed
Log("[AbstractIrcServer::addChannel] {} was destroyed",
log("[AbstractIrcServer::addChannel] {} was destroyed",
clojuresInCppAreShit);
this->channels.remove(clojuresInCppAreShit);
+3 -1
View File
@@ -1,6 +1,5 @@
#pragma once
#include "common/Channel.hpp"
#include "providers/irc/IrcConnection2.hpp"
#include <IrcMessage>
@@ -11,6 +10,9 @@
namespace chatterino {
class Channel;
using ChannelPtr = std::shared_ptr<Channel>;
class AbstractIrcServer
{
public:
+6 -5
View File
@@ -10,6 +10,7 @@
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Settings.hpp"
#include "singletons/WindowManager.hpp"
#include "util/IrcHelpers.hpp"
@@ -145,7 +146,7 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
if (chan->isEmpty()) {
Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
"found",
chanName);
return;
@@ -209,7 +210,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
{
auto app = getApp();
Log("Received whisper!");
log("Received whisper!");
MessageParseArgs args;
args.isReceivedWhisper = true;
@@ -230,7 +231,7 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
c->addMessage(_message);
if (app->settings->inlineWhispers) {
if (getSettings()->inlineWhispers) {
app->twitch.server->forEachChannel([_message](ChannelPtr channel) {
channel->addMessage(_message); //
});
@@ -326,7 +327,7 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
auto channel = app->twitch.server->getChannelOrEmpty(channelName);
if (channel->isEmpty()) {
Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
"manager ",
channelName);
return;
@@ -366,7 +367,7 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(
return;
}
Log("Showing notice message from write connection with message id '{}'",
log("Showing notice message from write connection with message id '{}'",
msgID);
}
+5 -4
View File
@@ -1,5 +1,6 @@
#include "providers/twitch/PartialTwitchUser.hpp"
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchCommon.hpp"
@@ -42,23 +43,23 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
request.onSuccess([successCallback](auto result) -> Outcome {
auto root = result.parseJson();
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;
}
auto users = root.value("users").toArray();
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;
}
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;
}
auto firstUser = users[0].toObject();
auto id = firstUser.value("_id");
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 "
"string");
return Failure;
+165 -163
View File
@@ -24,157 +24,159 @@ static std::map<QString, std::string> sentMessages;
namespace detail {
PubSubClient::PubSubClient(WebsocketClient &websocketClient,
WebsocketHandle handle)
: websocketClient_(websocketClient)
, handle_(handle)
{
}
void PubSubClient::start()
{
assert(!this->started_);
this->started_ = true;
this->ping();
}
void PubSubClient::stop()
{
assert(this->started_);
this->started_ = false;
}
bool PubSubClient::listen(rapidjson::Document &message)
{
int numRequestedListens = message["data"]["topics"].Size();
if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS) {
// This PubSubClient is already at its peak listens
return false;
PubSubClient::PubSubClient(WebsocketClient &websocketClient,
WebsocketHandle handle)
: websocketClient_(websocketClient)
, handle_(handle)
{
}
this->numListens_ += numRequestedListens;
void PubSubClient::start()
{
assert(!this->started_);
for (const auto &topic : message["data"]["topics"].GetArray()) {
this->listeners_.emplace_back(
Listener{topic.GetString(), false, false, false});
this->started_ = true;
this->ping();
}
auto uuid = CreateUUID();
void PubSubClient::stop()
{
assert(this->started_);
rj::set(message, "nonce", uuid);
this->started_ = false;
}
std::string payload = rj::stringify(message);
sentMessages[uuid] = payload;
bool PubSubClient::listen(rapidjson::Document &message)
{
int numRequestedListens = message["data"]["topics"].Size();
this->send(payload.c_str());
return true;
}
void PubSubClient::unlistenPrefix(const std::string &prefix)
{
std::vector<std::string> topics;
for (auto it = this->listeners_.begin(); it != this->listeners_.end();) {
const auto &listener = *it;
if (listener.topic.find(prefix) == 0) {
topics.push_back(listener.topic);
it = this->listeners_.erase(it);
} else {
++it;
if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS) {
// This PubSubClient is already at its peak listens
return false;
}
}
if (topics.empty()) {
return;
}
this->numListens_ += numRequestedListens;
auto message = createUnlistenMessage(topics);
auto uuid = CreateUUID();
rj::set(message, "nonce", CreateUUID());
std::string payload = rj::stringify(message);
sentMessages[uuid] = payload;
this->send(payload.c_str());
}
void PubSubClient::handlePong()
{
assert(this->awaitingPong_);
Log("Got pong!");
this->awaitingPong_ = false;
}
bool PubSubClient::isListeningToTopic(const std::string &payload)
{
for (const auto &listener : this->listeners_) {
if (listener.topic == payload) {
return true;
for (const auto &topic : message["data"]["topics"].GetArray()) {
this->listeners_.emplace_back(
Listener{topic.GetString(), false, false, false});
}
auto uuid = CreateUUID();
rj::set(message, "nonce", uuid);
std::string payload = rj::stringify(message);
sentMessages[uuid] = payload;
this->send(payload.c_str());
return true;
}
return false;
}
void PubSubClient::unlistenPrefix(const std::string &prefix)
{
std::vector<std::string> topics;
void PubSubClient::ping()
{
assert(this->started_);
for (auto it = this->listeners_.begin();
it != this->listeners_.end();) {
const auto &listener = *it;
if (listener.topic.find(prefix) == 0) {
topics.push_back(listener.topic);
it = this->listeners_.erase(it);
} else {
++it;
}
}
if (!this->send(pingPayload)) {
return;
if (topics.empty()) {
return;
}
auto message = createUnlistenMessage(topics);
auto uuid = CreateUUID();
rj::set(message, "nonce", CreateUUID());
std::string payload = rj::stringify(message);
sentMessages[uuid] = payload;
this->send(payload.c_str());
}
this->awaitingPong_ = true;
void PubSubClient::handlePong()
{
assert(this->awaitingPong_);
auto self = this->shared_from_this();
log("Got pong!");
runAfter(this->websocketClient_.get_io_service(), std::chrono::seconds(15),
[self](auto timer) {
if (!self->started_) {
return;
}
this->awaitingPong_ = false;
}
if (self->awaitingPong_) {
Log("No pong respnose, disconnect!");
// TODO(pajlada): Label this connection as "disconnect me"
}
});
runAfter(this->websocketClient_.get_io_service(), std::chrono::minutes(5),
[self](auto timer) {
if (!self->started_) {
return;
}
self->ping(); //
});
}
bool PubSubClient::send(const char *payload)
{
WebsocketErrorCode ec;
this->websocketClient_.send(this->handle_, payload,
websocketpp::frame::opcode::text, ec);
if (ec) {
Log("Error sending message {}: {}", payload, ec.message());
// TODO(pajlada): Check which error code happened and maybe gracefully
// handle it
bool PubSubClient::isListeningToTopic(const std::string &payload)
{
for (const auto &listener : this->listeners_) {
if (listener.topic == payload) {
return true;
}
}
return false;
}
return true;
}
void PubSubClient::ping()
{
assert(this->started_);
if (!this->send(pingPayload)) {
return;
}
this->awaitingPong_ = true;
auto self = this->shared_from_this();
runAfter(this->websocketClient_.get_io_service(),
std::chrono::seconds(15), [self](auto timer) {
if (!self->started_) {
return;
}
if (self->awaitingPong_) {
log("No pong respnose, disconnect!");
// TODO(pajlada): Label this connection as "disconnect
// me"
}
});
runAfter(this->websocketClient_.get_io_service(),
std::chrono::minutes(5), [self](auto timer) {
if (!self->started_) {
return;
}
self->ping(); //
});
}
bool PubSubClient::send(const char *payload)
{
WebsocketErrorCode ec;
this->websocketClient_.send(this->handle_, payload,
websocketpp::frame::opcode::text, ec);
if (ec) {
log("Error sending message {}: {}", payload, ec.message());
// TODO(pajlada): Check which error code happened and maybe
// gracefully handle it
return false;
}
return true;
}
} // namespace detail
@@ -207,26 +209,26 @@ PubSub::PubSub()
action.state = ModeChangedAction::State::On;
if (!data.HasMember("args")) {
Log("Missing required args member");
log("Missing required args member");
return;
}
const auto &args = data["args"];
if (!args.IsArray()) {
Log("args member must be an array");
log("args member must be an array");
return;
}
if (args.Size() == 0) {
Log("Missing duration argument in slowmode on");
log("Missing duration argument in slowmode on");
return;
}
const auto &durationArg = args[0];
if (!durationArg.IsString()) {
Log("Duration arg must be a string");
log("Duration arg must be a string");
return;
}
@@ -314,7 +316,7 @@ PubSub::PubSub()
return;
}
} catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what());
log("Error parsing moderation action: {}", ex.what());
}
action.modded = false;
@@ -339,7 +341,7 @@ PubSub::PubSub()
return;
}
} catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what());
log("Error parsing moderation action: {}", ex.what());
}
action.modded = true;
@@ -380,7 +382,7 @@ PubSub::PubSub()
this->signals_.moderation.userBanned.invoke(action);
} catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what());
log("Error parsing moderation action: {}", ex.what());
}
};
@@ -410,7 +412,7 @@ PubSub::PubSub()
this->signals_.moderation.userBanned.invoke(action);
} catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what());
log("Error parsing moderation action: {}", ex.what());
}
};
@@ -436,7 +438,7 @@ PubSub::PubSub()
this->signals_.moderation.userUnbanned.invoke(action);
} catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what());
log("Error parsing moderation action: {}", ex.what());
}
};
@@ -462,7 +464,7 @@ PubSub::PubSub()
this->signals_.moderation.userUnbanned.invoke(action);
} catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what());
log("Error parsing moderation action: {}", ex.what());
}
};
@@ -493,7 +495,7 @@ void PubSub::addClient()
auto con = this->websocketClient.get_connection(TWITCH_PUBSUB_URL, ec);
if (ec) {
Log("Unable to establish connection: {}", ec.message());
log("Unable to establish connection: {}", ec.message());
return;
}
@@ -512,7 +514,7 @@ void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
std::string userID = account->getUserId().toStdString();
Log("Connection open!");
log("Connection open!");
websocketpp::lib::error_code ec;
std::vector<std::string> topics({"whispers." + userID});
@@ -520,7 +522,7 @@ void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
this->listen(createListenMessage(topics, account));
if (ec) {
Log("Unable to send message to websocket server: {}", ec.message());
log("Unable to send message to websocket server: {}", ec.message());
return;
}
}
@@ -544,11 +546,11 @@ void PubSub::listenToChannelModerationActions(
std::string topic(fS("chat_moderator_actions.{}.{}", userID, channelID));
if (this->isListeningToTopic(topic)) {
Log("We are already listening to topic {}", topic);
log("We are already listening to topic {}", topic);
return;
}
Log("Listen to topic {}", topic);
log("Listen to topic {}", topic);
this->listenToTopic(topic, account);
}
@@ -564,18 +566,18 @@ void PubSub::listenToTopic(const std::string &topic,
void PubSub::listen(rapidjson::Document &&msg)
{
if (this->tryListen(msg)) {
Log("Successfully listened!");
log("Successfully listened!");
return;
}
Log("Added to the back of the queue");
log("Added to the back of the queue");
this->requests.emplace_back(
std::make_unique<rapidjson::Document>(std::move(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) {
const auto &client = p.second;
if (client->listen(msg)) {
@@ -608,13 +610,13 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
rapidjson::ParseResult res = msg.Parse(payload.c_str());
if (!res) {
Log("Error parsing message '{}' from PubSub: {}", payload,
log("Error parsing message '{}' from PubSub: {}", payload,
rapidjson::GetParseError_En(res.Code()));
return;
}
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",
payload);
return;
@@ -623,7 +625,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
std::string 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;
}
@@ -631,14 +633,14 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
this->handleListenResponse(msg);
} else if (type == "MESSAGE") {
if (!msg.HasMember("data")) {
Log("Missing required object member `data` in message root");
log("Missing required object member `data` in message root");
return;
}
const auto &data = msg["data"];
if (!data.IsObject()) {
Log("Member `data` must be an object");
log("Member `data` must be an object");
return;
}
@@ -654,7 +656,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
client.second->handlePong();
} else {
Log("Unknown message type: {}", type);
log("Unknown message type: {}", type);
}
}
@@ -699,7 +701,7 @@ PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::single_dh_use);
} catch (const std::exception &e) {
Log("Exception caught in OnTLSInit: {}", e.what());
log("Exception caught in OnTLSInit: {}", e.what());
}
return ctx;
@@ -714,12 +716,12 @@ void PubSub::handleListenResponse(const rapidjson::Document &msg)
rj::getSafe(msg, "nonce", nonce);
if (error.empty()) {
Log("Successfully listened to nonce {}", nonce);
log("Successfully listened to nonce {}", nonce);
// Nothing went wrong
return;
}
Log("PubSub error: {} on nonce {}", error, nonce);
log("PubSub error: {} on nonce {}", error, nonce);
return;
}
}
@@ -729,14 +731,14 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
QString topic;
if (!rj::getSafe(outerData, "topic", topic)) {
Log("Missing required string member `topic` in outerData");
log("Missing required string member `topic` in outerData");
return;
}
std::string payload;
if (!rj::getSafe(outerData, "message", payload)) {
Log("Expected string message in outerData");
log("Expected string message in outerData");
return;
}
@@ -745,7 +747,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
rapidjson::ParseResult res = msg.Parse(payload.c_str());
if (!res) {
Log("Error parsing message '{}' from PubSub: {}", payload,
log("Error parsing message '{}' from PubSub: {}", payload,
rapidjson::GetParseError_En(res.Code()));
return;
}
@@ -754,7 +756,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
std::string whisperType;
if (!rj::getSafe(msg, "type", whisperType)) {
Log("Bad whisper data");
log("Bad whisper data");
return;
}
@@ -765,7 +767,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
} else if (whisperType == "thread") {
// Handle thread?
} else {
Log("Invalid whisper type: {}", whisperType);
log("Invalid whisper type: {}", whisperType);
assert(false);
return;
}
@@ -777,30 +779,30 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
std::string 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;
}
auto handlerIt = this->moderationActionHandlers.find(moderationAction);
if (handlerIt == this->moderationActionHandlers.end()) {
Log("No handler found for moderation action {}", moderationAction);
log("No handler found for moderation action {}", moderationAction);
return;
}
// Invoke handler function
handlerIt->second(data, topicParts[2]);
} else {
Log("Unknown topic: {}", topic);
log("Unknown topic: {}", topic);
return;
}
}
void PubSub::runThread()
{
Log("Start pubsub manager thread");
log("Start pubsub manager thread");
this->websocketClient.run();
Log("Done with pubsub manager thread");
log("Done with pubsub manager thread");
}
} // namespace chatterino
+27 -26
View File
@@ -32,41 +32,42 @@ using WebsocketErrorCode = websocketpp::lib::error_code;
namespace detail {
struct Listener {
std::string topic;
bool authed;
bool persistent;
bool confirmed = false;
};
struct Listener {
std::string topic;
bool authed;
bool persistent;
bool confirmed = false;
};
class PubSubClient : public std::enable_shared_from_this<PubSubClient>
{
public:
PubSubClient(WebsocketClient &_websocketClient, WebsocketHandle _handle);
class PubSubClient : public std::enable_shared_from_this<PubSubClient>
{
public:
PubSubClient(WebsocketClient &_websocketClient,
WebsocketHandle _handle);
void start();
void stop();
void start();
void stop();
bool listen(rapidjson::Document &message);
void unlistenPrefix(const std::string &prefix);
bool listen(rapidjson::Document &message);
void unlistenPrefix(const std::string &prefix);
void handlePong();
void handlePong();
bool isListeningToTopic(const std::string &topic);
bool isListeningToTopic(const std::string &topic);
private:
void ping();
bool send(const char *payload);
private:
void ping();
bool send(const char *payload);
WebsocketClient &websocketClient_;
WebsocketHandle handle_;
uint16_t numListens_ = 0;
WebsocketClient &websocketClient_;
WebsocketHandle handle_;
uint16_t numListens_ = 0;
std::vector<Listener> listeners_;
std::vector<Listener> listeners_;
std::atomic<bool> awaitingPong_{false};
std::atomic<bool> started_{false};
};
std::atomic<bool> awaitingPong_{false};
std::atomic<bool> started_{false};
};
} // namespace detail
+1
View File
@@ -1,6 +1,7 @@
#include "providers/twitch/PubsubHelpers.hpp"
#include "providers/twitch/PubsubActions.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "util/RapidjsonHelpers.hpp"
namespace chatterino {
+5 -7
View File
@@ -1,16 +1,14 @@
#pragma once
#include "debug/Log.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "util/RapidjsonHelpers.hpp"
#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
#include <memory>
#include "debug/Log.hpp"
#include "util/RapidjsonHelpers.hpp"
namespace chatterino {
class TwitchAccount;
struct ActionUser;
const rapidjson::Value &getArgs(const rapidjson::Value &data);
@@ -35,7 +33,7 @@ void runAfter(boost::asio::io_service &ioService, Duration duration,
timer->async_wait([timer, cb](const boost::system::error_code &ec) {
if (ec) {
Log("Error in runAfter: {}", ec.message());
log("Error in runAfter: {}", ec.message());
return;
}
@@ -52,7 +50,7 @@ void runAfter(std::shared_ptr<boost::asio::steady_timer> timer,
timer->async_wait([timer, cb](const boost::system::error_code &ec) {
if (ec) {
Log("Error in runAfter: {}", ec.message());
log("Error in runAfter: {}", ec.message());
return;
}
+51 -41
View File
@@ -4,6 +4,7 @@
#include "Application.hpp"
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/PartialTwitchUser.hpp"
#include "providers/twitch/TwitchCommon.hpp"
@@ -14,32 +15,32 @@ namespace chatterino {
namespace {
EmoteName cleanUpCode(const EmoteName &dirtyEmoteCode)
{
auto cleanCode = dirtyEmoteCode.string;
cleanCode.detach();
EmoteName cleanUpCode(const EmoteName &dirtyEmoteCode)
{
auto cleanCode = dirtyEmoteCode.string;
cleanCode.detach();
static QMap<QString, QString> emoteNameReplacements{
{"[oO](_|\\.)[oO]", "O_o"}, {"\\&gt\\;\\(", "&gt;("},
{"\\&lt\\;3", "&lt;3"}, {"\\:-?(o|O)", ":O"},
{"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("},
{"\\:-?\\)", ":)"}, {"\\:-?D", ":D"},
{"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
{"R-?\\)", "R)"}, {"B-?\\)", "B)"},
};
static QMap<QString, QString> emoteNameReplacements{
{"[oO](_|\\.)[oO]", "O_o"}, {"\\&gt\\;\\(", "&gt;("},
{"\\&lt\\;3", "&lt;3"}, {"\\:-?(o|O)", ":O"},
{"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("},
{"\\:-?\\)", ":)"}, {"\\:-?D", ":D"},
{"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
{"R-?\\)", "R)"}, {"B-?\\)", "B)"},
};
auto it = emoteNameReplacements.find(dirtyEmoteCode.string);
if (it != emoteNameReplacements.end()) {
cleanCode = it.value();
auto it = emoteNameReplacements.find(dirtyEmoteCode.string);
if (it != emoteNameReplacements.end()) {
cleanCode = it.value();
}
cleanCode.replace("&lt;", "<");
cleanCode.replace("&gt;", ">");
return {cleanCode};
}
cleanCode.replace("&lt;", "<");
cleanCode.replace("&gt;", ">");
return {cleanCode};
}
} // namespace
TwitchAccount::TwitchAccount(const QString &username, const QString &oauthToken,
@@ -78,6 +79,16 @@ const QString &TwitchAccount::getUserId() const
return this->userId_;
}
QColor TwitchAccount::color()
{
return this->color_.get();
}
void TwitchAccount::setColor(QColor color)
{
this->color_.set(color);
}
bool TwitchAccount::setOAuthClient(const QString &newClientID)
{
if (this->oauthClient_.compare(newClientID) == 0) {
@@ -143,7 +154,7 @@ void TwitchAccount::loadIgnores()
}
TwitchUser ignoredUser;
if (!rj::getSafe(userIt->value, ignoredUser)) {
Log("Error parsing twitch user JSON {}",
log("Error parsing twitch user JSON {}",
rj::stringify(userIt->value));
continue;
}
@@ -368,13 +379,13 @@ std::set<TwitchUser> TwitchAccount::getIgnores() const
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 &oauthToken = this->getOAuthToken();
if (clientID.isEmpty() || oauthToken.isEmpty()) {
Log("Missing Client ID or OAuth token");
log("Missing Client ID or OAuth token");
return;
}
@@ -386,7 +397,7 @@ void TwitchAccount::loadEmotes()
req.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken());
req.onError([=](int errorCode) {
Log("[TwitchAccount::loadEmotes] Error {}", errorCode);
log("[TwitchAccount::loadEmotes] Error {}", errorCode);
if (errorCode == 203) {
// onFinished(FollowResult_NotFollowing);
} else {
@@ -406,7 +417,7 @@ void TwitchAccount::loadEmotes()
}
AccessGuard<const TwitchAccount::TwitchAccountEmoteData>
TwitchAccount::accessEmotes() const
TwitchAccount::accessEmotes() const
{
return this->emotes_.accessConst();
}
@@ -420,7 +431,7 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
auto emoticonSets = root.FindMember("emoticon_sets");
if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject()) {
Log("No emoticon_sets in load emotes response");
log("No emoticon_sets in load emotes response");
return;
}
@@ -434,19 +445,19 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
for (const rapidjson::Value &emoteJSON :
emoteSetJSON.value.GetArray()) {
if (!emoteJSON.IsObject()) {
Log("Emote value was invalid");
log("Emote value was invalid");
return;
}
uint64_t idNumber;
if (!rj::getSafe(emoteJSON, "id", idNumber)) {
Log("No ID key found in Emote value");
log("No ID key found in Emote value");
return;
}
QString _code;
if (!rj::getSafe(emoteJSON, "code", _code)) {
Log("No code key found in Emote value");
log("No code key found in Emote value");
return;
}
@@ -468,7 +479,7 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
{
if (!emoteSet) {
Log("null emote set sent");
log("null emote set sent");
return;
}
@@ -486,7 +497,7 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
req.setUseQuickLoadCache(true);
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;
});
@@ -507,16 +518,15 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
return Failure;
}
Log("Loaded twitch emote set data for {}!", emoteSet->key);
log("Loaded twitch emote set data for {}!", emoteSet->key);
if (type == "sub") {
emoteSet->text =
QString("Twitch Subscriber Emote (%1)").arg(channelName);
} else {
emoteSet->text =
QString("Twitch Account Emote (%1)").arg(channelName);
}
auto name = channelName;
name.detach();
name[0] = name[0].toUpper();
emoteSet->text = name;
emoteSet->type = type;
emoteSet->channelName = channelName;
return Success;
+7 -3
View File
@@ -1,5 +1,7 @@
#pragma once
#include "common/Aliases.hpp"
#include "common/Atomic.hpp"
#include "common/UniqueAccess.hpp"
#include "controllers/accounts/Account.hpp"
#include "messages/Emote.hpp"
@@ -44,6 +46,7 @@ public:
QString key;
QString channelName;
QString text;
QString type;
std::vector<TwitchEmote> emotes;
};
@@ -65,9 +68,11 @@ public:
const QString &getUserName() const;
const QString &getOAuthToken() const;
const QString &getOAuthClient() const;
const QString &getUserId() const;
QColor color();
void setColor(QColor color);
// Attempts to update the users OAuth Client ID
// Returns true if the value has changed, otherwise false
bool setOAuthClient(const QString &newClientID);
@@ -103,8 +108,6 @@ public:
void loadEmotes();
AccessGuard<const TwitchAccountEmoteData> accessEmotes() const;
QColor color;
private:
void parseEmotes(const rapidjson::Document &document);
void loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet);
@@ -114,6 +117,7 @@ private:
QString userName_;
QString userId_;
const bool isAnon_;
Atomic<QColor> color_;
mutable std::mutex ignoresMutex_;
std::set<TwitchUser> ignores_;

Some files were not shown because too many files have changed in this diff Show More