diff --git a/account.cpp b/account.cpp new file mode 100644 index 000000000..f414a282c --- /dev/null +++ b/account.cpp @@ -0,0 +1,10 @@ +#include "account.h" + +const Account* Account::m_anon = new Account("justinfan123", "", ""); + +Account::Account(QString username, QString oauthToken, QString oauthClient) +{ + m_oauthClient = oauthClient; + m_oauthToken = oauthToken; + m_username = username; +} diff --git a/account.h b/account.h new file mode 100644 index 000000000..7bca5c648 --- /dev/null +++ b/account.h @@ -0,0 +1,39 @@ +#ifndef ACCOUNT_H +#define ACCOUNT_H + +#include "QString" + +class Account +{ +public: + Account(QString username, QString oauthToken, QString oauthClient); + + static const Account* anon() { + return m_anon; + } + + const QString& username() { + return m_username; + } + + const QString& oauthToken() { + return m_oauthToken; + } + + const QString& oauthClient() { + return m_oauthClient; + } + + bool isAnon() { + return m_username.startsWith("justinfan"); + } + +private: + const static Account* m_anon; + + QString m_username; + QString m_oauthClient; + QString m_oauthToken; +}; + +#endif // ACCOUNT_H diff --git a/asyncexec.h b/asyncexec.h new file mode 100644 index 000000000..81142b4cc --- /dev/null +++ b/asyncexec.h @@ -0,0 +1,14 @@ +#ifndef ASYNCEXEC_H +#define ASYNCEXEC_H + +#include "QThreadPool" +#include "QRunnable" +#include "lambdaqrunnable.h" +#include "qcoreapplication.h" + +#define async_start QThreadPool::globalInstance()->start(new LambdaQRunnable( +#define async_end )); +#define async_exec(a) QThreadPool::globalInstance()->start(new LambdaQRunnable([]{ a; })); + + +#endif // ASYNCEXEC_H diff --git a/chatterino.pro b/chatterino.pro index d3701e6b4..e93d6a1da 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -46,7 +46,11 @@ SOURCES += main.cpp\ scrollbar.cpp \ scrollbarhighlight.cpp \ ircmanager.cpp \ - lambdaqrunnable.cpp + lambdaqrunnable.cpp \ + account.cpp \ + emotes.cpp \ + lazyloadedimage.cpp \ + concurrentmap.cpp HEADERS += mainwindow.h \ chatwidget.h \ @@ -66,7 +70,13 @@ HEADERS += mainwindow.h \ scrollbar.h \ scrollbarhighlight.h \ ircmanager.h \ - lambdaqrunnable.h + lambdaqrunnable.h \ + asyncexec.h \ + account.h \ + emotes.h \ + lazyloadedimage.h \ + twitchemotevalue.h \ + concurrentmap.h FORMS += \ dialog.ui diff --git a/concurrentmap.cpp b/concurrentmap.cpp new file mode 100644 index 000000000..fa2365bf6 --- /dev/null +++ b/concurrentmap.cpp @@ -0,0 +1,7 @@ +#include "concurrentmap.h" + +//template +//ConcurrentMap::ConcurrentMap() +//{ + +//} diff --git a/concurrentmap.h b/concurrentmap.h new file mode 100644 index 000000000..613872baf --- /dev/null +++ b/concurrentmap.h @@ -0,0 +1,60 @@ +#ifndef CONCURRENTMAP_H +#define CONCURRENTMAP_H + +#include "QMutex" +#include "QMap" +#include "functional" + +template +class ConcurrentMap +{ +public: + ConcurrentMap() { + mutex = new QMutex(); + map = new QMap(); + } + + bool tryGet(const TKey &name, TValue& value) { + mutex->lock(); + auto a = map->find(name); + if (a == map->end()) { + mutex->unlock(); + value = NULL; + return false; + } + mutex->unlock(); + value = a.value(); + return true; + } + + TValue getOrAdd(const TKey &name, function addLambda) { + mutex->lock(); + auto a = map->find(name); + if (a == map->end()) { + TValue value = addLambda(); + map->insert(name, value); + mutex->unlock(); + return value; + } + mutex->unlock(); + return a.value(); + } + + void clear() { + mutex->lock(); + map->clear(); + mutex->unlock(); + } + + void insert(const TKey &name, const TValue &value) { + mutex->lock(); + map->insert(name, value); + mutex->unlock(); + } + +private: + QMutex* mutex; + QMap* map; +}; + +#endif // CONCURRENTMAP_H diff --git a/emotes.cpp b/emotes.cpp new file mode 100644 index 000000000..b2b84d5d0 --- /dev/null +++ b/emotes.cpp @@ -0,0 +1,32 @@ +#include "emotes.h" + +ConcurrentMap* Emotes::m_twitchEmotes = new ConcurrentMap(); +ConcurrentMap* Emotes::m_bttvEmotes = new ConcurrentMap(); +ConcurrentMap* Emotes::m_ffzEmotes = new ConcurrentMap(); +ConcurrentMap* Emotes::m_chatterinoEmotes = new ConcurrentMap(); +ConcurrentMap* Emotes::m_bttvChannelEmoteFromCaches = new ConcurrentMap(); +ConcurrentMap* Emotes::m_fFzChannelEmoteFromCaches = new ConcurrentMap(); +ConcurrentMap* Emotes::m_twitchEmoteFromCache = new ConcurrentMap(); +ConcurrentMap* Emotes::m_miscImageFromCache = new ConcurrentMap(); + +//QMutex* Emotes::mutexBttvEmote = new QMutex(); +//QMap* Emotes::mapBttvEmote = new QMap(); + +//LazyLoadedImage* Emotes::getBttvEmote(const QString &name) { +// mutexBttvEmote->lock(); +// auto a = mapBttvEmote->find(name); +// if (a == mapBttvEmote->end()) { +// mutexBttvEmote->unlock(); +// return NULL; +// } +// mutexBttvEmote->unlock(); +// return a.value(); +//} + +//void + + +Emotes::Emotes() +{ + +} diff --git a/emotes.h b/emotes.h new file mode 100644 index 000000000..4efe7c2a4 --- /dev/null +++ b/emotes.h @@ -0,0 +1,35 @@ +#ifndef EMOTES_H +#define EMOTES_H + +#include "twitchemotevalue.h" +#include "lazyloadedimage.h" +#include "QMutex" +#include "QMap" +#include "concurrentmap.h" + +class Emotes +{ +public: + static ConcurrentMap& twitchEmotes() { return *m_twitchEmotes ; } + static ConcurrentMap& bttvEmotes() { return *m_bttvEmotes ; } + static ConcurrentMap& ffzEmotes() { return *m_ffzEmotes ; } + static ConcurrentMap& chatterinoEmotes() { return *m_chatterinoEmotes ; } + static ConcurrentMap& bttvChannelEmoteFromCaches() { return *m_bttvChannelEmoteFromCaches; } + static ConcurrentMap& fFzChannelEmoteFromCaches() { return *m_fFzChannelEmoteFromCaches ; } + static ConcurrentMap& twitchEmoteFromCache() { return *m_twitchEmoteFromCache ; } + static ConcurrentMap& miscImageFromCache() { return *m_miscImageFromCache ; } + +private: + Emotes(); + + static ConcurrentMap* m_twitchEmotes; + static ConcurrentMap* m_bttvEmotes; + static ConcurrentMap* m_ffzEmotes; + static ConcurrentMap* m_chatterinoEmotes; + static ConcurrentMap* m_bttvChannelEmoteFromCaches; + static ConcurrentMap* m_fFzChannelEmoteFromCaches; + static ConcurrentMap* m_twitchEmoteFromCache; + static ConcurrentMap* m_miscImageFromCache; +}; + +#endif // EMOTES_H diff --git a/ircmanager.cpp b/ircmanager.cpp index 3d30672d9..a74eaa33b 100644 --- a/ircmanager.cpp +++ b/ircmanager.cpp @@ -2,27 +2,33 @@ #include "ircconnection.h" #include "irccommand.h" #include "future" -#include "QThreadPool" -#include "QRunnable" -#include "lambdaqrunnable.h" -#include "qcoreapplication.h" +#include "QNetworkReply" +#include "asyncexec.h" +#include "qnetworkrequest.h" +#include "QJsonDocument" +#include "QJsonObject" +#include "QJsonArray" -IrcConnection* IrcManager::connection = NULL; -QMutex* IrcManager::connectionMutex = new QMutex(); -long IrcManager::connectionIteration = 0; +Account* IrcManager::account = NULL; +IrcConnection* IrcManager::connection = NULL; +QMutex* IrcManager::connectionMutex = new QMutex(); +long IrcManager::connectionIteration = 0; +const QString IrcManager::defaultClientId = "7ue61iz46fz11y3cugd0l3tawb4taal"; +QNetworkAccessManager* IrcManager::accessManager = new QNetworkAccessManager(); -QObject* IrcManager::parent = new QObject(); +QMap* IrcManager::twitchBlockedUsers = new QMap; +QMutex* IrcManager::twitchBlockedUsersMutex = new QMutex(); IrcManager::IrcManager() { - +// account = Account::anon(); } void IrcManager::connect() { disconnect(); - QThreadPool::globalInstance()->start(new LambdaQRunnable([]{ beginConnecting(); return false; })); + async_exec(beginConnecting()); } void IrcManager::beginConnecting() @@ -38,6 +44,68 @@ void IrcManager::beginConnecting() &IrcConnection::privateMessageReceived, &privateMessageReceived); + if (account->isAnon()) { + // fetch ignored users + QString username = account->username(); + QString oauthClient = account->oauthClient(); + QString oauthToken = account->oauthToken(); + + { + QString nextLink = "https://api.twitch.tv/kraken/users/" + username + + "/blocks?limit=" + 100 + + "&client_id=" + oauthClient; + + QNetworkRequest req(QUrl(nextLink + "&oauth_token=" + oauthToken)); + QNetworkReply *reply = accessManager->get(req); + + QObject::connect(reply, &QNetworkReply::finished, [=]{ + twitchBlockedUsersMutex->lock(); + twitchBlockedUsers->clear(); + twitchBlockedUsersMutex->unlock(); + + QByteArray data = reply->readAll(); + QJsonDocument jsonDoc(QJsonDocument::fromJson(data)); + QJsonObject root = jsonDoc.object(); + + //nextLink = root.value("_links").toObject().value("next").toString(); + + auto blocks = root.value("blocks").toArray(); + + twitchBlockedUsersMutex->lock(); + for (QJsonValue block : blocks) { + QJsonObject user = block.toObject().value("user").toObject(); + // display_name + twitchBlockedUsers->insert(user.value("name").toString().toLower(), true); + } + twitchBlockedUsersMutex->unlock(); + }); + } + + // fetch available twitch emtoes + { + QNetworkRequest req(QUrl("https://api.twitch.tv/kraken/users/" + username + "/emotes?oauth_token=" + oauthToken + "&client_id=" + oauthClient)); + QNetworkReply *reply = accessManager->get(req); + + QObject::connect(reply, &QNetworkReply::finished, [=]{ + QByteArray data = reply->readAll(); + QJsonDocument jsonDoc(QJsonDocument::fromJson(data)); + QJsonObject root = jsonDoc.object(); + + //nextLink = root.value("_links").toObject().value("next").toString(); + + auto blocks = root.value("blocks").toArray(); + + twitchBlockedUsersMutex->lock(); + for (QJsonValue block : blocks) { + QJsonObject user = block.toObject().value("user").toObject(); + // display_name + twitchBlockedUsers->insert(user.value("name").toString().toLower(), true); + } + twitchBlockedUsersMutex->unlock(); + }); + } + } + c->setHost("irc.chat.twitch.tv"); c->setPort(6667); @@ -77,10 +145,93 @@ void IrcManager::disconnect() void IrcManager::messageReceived(IrcMessage *message) { -// qInfo(message->()); + qInfo(message->command().toStdString().c_str()); + +// if (message->command() == "") } void IrcManager::privateMessageReceived(IrcPrivateMessage *message) { qInfo(message->content().toStdString().c_str()); } + +bool IrcManager::isTwitchBlockedUser(QString const &username) +{ + twitchBlockedUsersMutex->lock(); + + auto iterator = twitchBlockedUsers->find(username); + + if (iterator == twitchBlockedUsers->end()) { + twitchBlockedUsersMutex->unlock(); + return false; + } + + twitchBlockedUsersMutex->unlock(); + return true; +} + +bool IrcManager::tryAddIgnoredUser(QString const &username, QString& errorMessage) +{ + QUrl url("https://api.twitch.tv/kraken/users/" + account->username() + + "/blocks/" + username + + "?oauth_token=" + account->oauthToken() + + "&client_id=" + account->oauthClient()); + + QNetworkRequest request(url); + auto reply = accessManager->put(request, QByteArray()); + reply->waitForReadyRead(10000); + + if (reply->error() == QNetworkReply::NoError) + { + twitchBlockedUsersMutex->lock(); + twitchBlockedUsers->insert(username, true); + twitchBlockedUsersMutex->unlock(); + + delete reply; + return true; + } + + errorMessage = "Error while ignoring user \"" + username + "\": " + reply->errorString(); + return false; +} + +void IrcManager::addIgnoredUser(QString const &username) +{ + QString errorMessage; + if (tryAddIgnoredUser(username, errorMessage)) { +#warning "xD" + } +} + +bool IrcManager::tryRemoveIgnoredUser(QString const &username, QString& errorMessage) +{ + QUrl url("https://api.twitch.tv/kraken/users/" + account->username() + + "/blocks/" + username + + "?oauth_token=" + account->oauthToken() + + "&client_id=" + account->oauthClient()); + + QNetworkRequest request(url); + auto reply = accessManager->deleteResource(request); + reply->waitForReadyRead(10000); + + if (reply->error() == QNetworkReply::NoError) + { + twitchBlockedUsersMutex->lock(); + twitchBlockedUsers->remove(username); + twitchBlockedUsersMutex->unlock(); + + delete reply; + return true; + } + + errorMessage = "Error while unignoring user \"" + username + "\": " + reply->errorString(); + return false; +} + +void IrcManager::removeIgnoredUser(QString const &username) +{ + QString errorMessage; + if (tryRemoveIgnoredUser(username, errorMessage)) { +#warning "xD" + } +} diff --git a/ircmanager.h b/ircmanager.h index f64baf339..ec9e525d0 100644 --- a/ircmanager.h +++ b/ircmanager.h @@ -5,6 +5,10 @@ #include "IrcMessage" #include "QMutex" +#include "QString" +#include "QMap" +#include "account.h" +#include "qnetworkaccessmanager.h" class IrcManager { @@ -12,12 +16,25 @@ public: static void connect(); static void disconnect(); + static const QString defaultClientId; + + bool isTwitchBlockedUser(QString const &username); + bool tryAddIgnoredUser(QString const &username, QString& errorMessage); + void addIgnoredUser(QString const &username); + bool tryRemoveIgnoredUser(QString const &username, QString& errorMessage); + void removeIgnoredUser(QString const &username); + + static Account* account; + private: IrcManager(); - static void beginConnecting(); + static QMap* twitchBlockedUsers; + static QMutex* twitchBlockedUsersMutex; - static QObject* parent; + static QNetworkAccessManager* accessManager; + + static void beginConnecting(); static IrcConnection* connection; static QMutex* connectionMutex; diff --git a/lambdaqrunnable.cpp b/lambdaqrunnable.cpp index 4051b4afd..9a7ccc22e 100644 --- a/lambdaqrunnable.cpp +++ b/lambdaqrunnable.cpp @@ -1,6 +1,6 @@ #include "lambdaqrunnable.h" -LambdaQRunnable::LambdaQRunnable(std::function action) +LambdaQRunnable::LambdaQRunnable(std::function action) { this->action = action; } diff --git a/lambdaqrunnable.h b/lambdaqrunnable.h index 289097c0f..d4af93e88 100644 --- a/lambdaqrunnable.h +++ b/lambdaqrunnable.h @@ -7,12 +7,12 @@ class LambdaQRunnable : public QRunnable { public: - LambdaQRunnable(std::function action); + LambdaQRunnable(std::function action); void run(); private: - std::function action; + std::function action; }; #endif // LAMBDAQRUNNABLE_H diff --git a/lazyloadedimage.cpp b/lazyloadedimage.cpp new file mode 100644 index 000000000..0b8092a0c --- /dev/null +++ b/lazyloadedimage.cpp @@ -0,0 +1,6 @@ +#include "lazyloadedimage.h" + +LazyLoadedImage::LazyLoadedImage() +{ + +} diff --git a/lazyloadedimage.h b/lazyloadedimage.h new file mode 100644 index 000000000..cb4e5e993 --- /dev/null +++ b/lazyloadedimage.h @@ -0,0 +1,11 @@ +#ifndef LAZYLOADEDIMAGE_H +#define LAZYLOADEDIMAGE_H + + +class LazyLoadedImage +{ +public: + LazyLoadedImage(); +}; + +#endif // LAZYLOADEDIMAGE_H \ No newline at end of file diff --git a/twitchemotevalue.h b/twitchemotevalue.h new file mode 100644 index 000000000..9c0027183 --- /dev/null +++ b/twitchemotevalue.h @@ -0,0 +1,27 @@ +#ifndef TWITCHEMOTEVALUE_H +#define TWITCHEMOTEVALUE_H + +#include "QString" + +struct TwitchEmoteValue +{ +public: + int set() { + return m_set; + } + + int id() { + return m_id; + } + + QString channelName() { + return m_channelName; + } + +private: + int m_set; + int m_id; + QString m_channelName; +}; + +#endif // TWITCHEMOTEVALUE_H