Merge branch 'master' into irc-support

This commit is contained in:
fourtf
2019-09-18 13:03:16 +02:00
204 changed files with 17026 additions and 16467 deletions
+9 -3
View File
@@ -48,9 +48,15 @@ void ChatroomChannel::refreshFFZChannelEmotes()
{
return;
}
FfzEmotes::loadChannel(this->chatroomOwnerId, [this](auto &&emoteMap) {
this->ffzEmotes_.set(std::make_shared<EmoteMap>(std::move(emoteMap)));
});
FfzEmotes::loadChannel(
this->chatroomOwnerId,
[this](auto &&emoteMap) {
this->ffzEmotes_.set(
std::make_shared<EmoteMap>(std::move(emoteMap)));
},
[this](auto &&modBadge) {
this->ffzCustomModBadge_.set(std::move(modBadge));
});
}
const QString &ChatroomChannel::getDisplayName() const
File diff suppressed because it is too large Load Diff
+58 -58
View File
@@ -1,58 +1,58 @@
#pragma once
#include <IrcMessage>
#include "messages/Message.hpp"
namespace chatterino {
class TwitchIrcServer;
class Channel;
class IrcMessageHandler
{
IrcMessageHandler() = default;
public:
static IrcMessageHandler &getInstance();
// parseMessage parses a single IRC message into 0+ Chatterino messages
std::vector<MessagePtr> parseMessage(Channel *channel,
Communi::IrcMessage *message);
// parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages
std::vector<MessagePtr> parsePrivMessage(
Channel *channel, Communi::IrcPrivateMessage *message);
void handlePrivMessage(Communi::IrcPrivateMessage *message,
TwitchIrcServer &server);
void handleRoomStateMessage(Communi::IrcMessage *message);
void handleClearChatMessage(Communi::IrcMessage *message);
void handleClearMessageMessage(Communi::IrcMessage *message);
void handleUserStateMessage(Communi::IrcMessage *message);
void handleWhisperMessage(Communi::IrcMessage *message);
// parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+
// chatterino messages
std::vector<MessagePtr> parseUserNoticeMessage(
Channel *channel, Communi::IrcMessage *message);
void handleUserNoticeMessage(Communi::IrcMessage *message,
TwitchIrcServer &server);
void handleModeMessage(Communi::IrcMessage *message);
// parseNoticeMessage parses a single IRC NOTICE message into 0+ chatterino
// messages
std::vector<MessagePtr> parseNoticeMessage(
Communi::IrcNoticeMessage *message);
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
void handleJoinMessage(Communi::IrcMessage *message);
void handlePartMessage(Communi::IrcMessage *message);
private:
void addMessage(Communi::IrcMessage *message, const QString &target,
const QString &content, TwitchIrcServer &server, bool isResub,
bool isAction);
};
} // namespace chatterino
#pragma once
#include <IrcMessage>
#include "messages/Message.hpp"
namespace chatterino {
class TwitchIrcServer;
class Channel;
class IrcMessageHandler
{
IrcMessageHandler() = default;
public:
static IrcMessageHandler &getInstance();
// parseMessage parses a single IRC message into 0+ Chatterino messages
std::vector<MessagePtr> parseMessage(Channel *channel,
Communi::IrcMessage *message);
// parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages
std::vector<MessagePtr> parsePrivMessage(
Channel *channel, Communi::IrcPrivateMessage *message);
void handlePrivMessage(Communi::IrcPrivateMessage *message,
TwitchIrcServer &server);
void handleRoomStateMessage(Communi::IrcMessage *message);
void handleClearChatMessage(Communi::IrcMessage *message);
void handleClearMessageMessage(Communi::IrcMessage *message);
void handleUserStateMessage(Communi::IrcMessage *message);
void handleWhisperMessage(Communi::IrcMessage *message);
// parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+
// chatterino messages
std::vector<MessagePtr> parseUserNoticeMessage(
Channel *channel, Communi::IrcMessage *message);
void handleUserNoticeMessage(Communi::IrcMessage *message,
TwitchIrcServer &server);
void handleModeMessage(Communi::IrcMessage *message);
// parseNoticeMessage parses a single IRC NOTICE message into 0+ chatterino
// messages
std::vector<MessagePtr> parseNoticeMessage(
Communi::IrcNoticeMessage *message);
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
void handleJoinMessage(Communi::IrcMessage *message);
void handlePartMessage(Communi::IrcMessage *message);
private:
void addMessage(Communi::IrcMessage *message, const QString &target,
const QString &content, TwitchIrcServer &server,
bool isResub, bool isAction);
};
} // namespace chatterino
+9 -9
View File
@@ -95,7 +95,7 @@ void TwitchAccount::loadIgnores()
"/blocks");
NetworkRequest(url)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onSuccess([=](auto result) -> Outcome {
auto document = result.parseRapidJson();
@@ -168,7 +168,7 @@ void TwitchAccount::ignoreByID(
"/blocks/" + targetUserID);
NetworkRequest(url, NetworkRequestType::Put)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](int errorCode) {
onFinished(IgnoreResult_Failed,
@@ -245,7 +245,7 @@ void TwitchAccount::unignoreByID(
"/blocks/" + targetUserID);
NetworkRequest(url, NetworkRequestType::Delete)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](int errorCode) {
onFinished(
@@ -279,7 +279,7 @@ void TwitchAccount::checkFollow(const QString targetUserID,
"/follows/channels/" + targetUserID);
NetworkRequest(url)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](int errorCode) {
if (errorCode == 203)
@@ -308,7 +308,7 @@ void TwitchAccount::followUser(const QString userID,
"/follows/channels/" + userID);
NetworkRequest(requestUrl, NetworkRequestType::Put)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onSuccess([successCallback](auto result) -> Outcome {
// TODO: Properly check result of follow request
@@ -326,7 +326,7 @@ void TwitchAccount::unfollowUser(const QString userID,
"/follows/channels/" + userID);
NetworkRequest(requestUrl, NetworkRequestType::Delete)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([successCallback](int code) {
if (code >= 200 && code <= 299)
@@ -368,7 +368,7 @@ void TwitchAccount::loadEmotes()
"/emotes");
NetworkRequest(url)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](int errorCode) {
log("[TwitchAccount::loadEmotes] Error {}", errorCode);
@@ -409,7 +409,7 @@ void TwitchAccount::autoModAllow(const QString msgID)
.header("Content-Type", "application/json")
.header("Content-Length", QByteArray::number(qba.size()))
.payload(qba)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](int errorCode) {
log("[TwitchAccounts::autoModAllow] Error {}", errorCode);
@@ -429,7 +429,7 @@ void TwitchAccount::autoModDeny(const QString msgID)
.header("Content-Type", "application/json")
.header("Content-Length", QByteArray::number(qba.size()))
.payload(qba)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](int errorCode) {
log("[TwitchAccounts::autoModDeny] Error {}", errorCode);
+234 -234
View File
@@ -1,234 +1,234 @@
#include "providers/twitch/TwitchAccountManager.hpp"
#include "common/Common.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchCommon.hpp"
namespace chatterino {
TwitchAccountManager::TwitchAccountManager()
: anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
{
this->currentUserChanged.connect([this] {
auto currentUser = this->getCurrent();
currentUser->loadIgnores();
});
this->accounts.itemRemoved.connect([this](const auto &acc) { //
this->removeUser(acc.item.get());
});
}
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
{
if (!this->currentUser_)
{
return this->anonymousUser_;
}
return this->currentUser_;
}
std::vector<QString> TwitchAccountManager::getUsernames() const
{
std::vector<QString> userNames;
std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts)
{
userNames.push_back(user->getUserName());
}
return userNames;
}
std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
const QString &username) const
{
std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts)
{
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0)
{
return user;
}
}
return nullptr;
}
bool TwitchAccountManager::userExists(const QString &username) const
{
return this->findUserByUsername(username) != nullptr;
}
void TwitchAccountManager::reloadUsers()
{
auto keys = pajlada::Settings::SettingManager::getObjectKeys("/accounts");
UserData userData;
bool listUpdated = false;
for (const auto &uid : keys)
{
if (uid == "current")
{
continue;
}
auto username = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/username");
auto userID = pajlada::Settings::Setting<QString>::get("/accounts/" +
uid + "/userID");
auto clientID = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/clientID");
auto oauthToken = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/oauthToken");
if (username.isEmpty() || userID.isEmpty() || clientID.isEmpty() ||
oauthToken.isEmpty())
{
continue;
}
userData.username = username.trimmed();
userData.userID = userID.trimmed();
userData.clientID = clientID.trimmed();
userData.oauthToken = oauthToken.trimmed();
switch (this->addUser(userData))
{
case AddUserResponse::UserAlreadyExists:
{
log("User {} already exists", userData.username);
// Do nothing
}
break;
case AddUserResponse::UserValuesUpdated:
{
log("User {} already exists, and values updated!",
userData.username);
if (userData.username == this->getCurrent()->getUserName())
{
log("It was the current user, so we need to reconnect "
"stuff!");
this->currentUserChanged.invoke();
}
}
break;
case AddUserResponse::UserAdded:
{
log("Added user {}", userData.username);
listUpdated = true;
}
break;
}
}
if (listUpdated)
{
this->userListUpdated.invoke();
}
}
void TwitchAccountManager::load()
{
this->reloadUsers();
this->currentUsername.connect([this](const QString &newUsername) {
auto user = this->findUserByUsername(newUsername);
if (user)
{
log("[AccountManager:currentUsernameChanged] User successfully "
"updated to {}",
newUsername);
this->currentUser_ = user;
}
else
{
log("[AccountManager:currentUsernameChanged] User successfully "
"updated to anonymous");
this->currentUser_ = this->anonymousUser_;
}
this->currentUserChanged.invoke();
});
}
bool TwitchAccountManager::isLoggedIn() const
{
if (!this->currentUser_)
{
return false;
}
// Once `TwitchAccount` class has a way to check, we should also return
// false if the credentials are incorrect
return !this->currentUser_->isAnon();
}
bool TwitchAccountManager::removeUser(TwitchAccount *account)
{
auto userID(account->getUserId());
if (!userID.isEmpty())
{
pajlada::Settings::SettingManager::removeSetting(
fS("/accounts/uid{}", userID));
}
if (account->getUserName() == this->currentUsername)
{
// The user that was removed is the current user, log into the anonymous
// user
this->currentUsername = "";
}
this->userListUpdated.invoke();
return true;
}
TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
const TwitchAccountManager::UserData &userData)
{
auto previousUser = this->findUserByUsername(userData.username);
if (previousUser)
{
bool userUpdated = false;
if (previousUser->setOAuthClient(userData.clientID))
{
userUpdated = true;
}
if (previousUser->setOAuthToken(userData.oauthToken))
{
userUpdated = true;
}
if (userUpdated)
{
return AddUserResponse::UserValuesUpdated;
}
else
{
return AddUserResponse::UserAlreadyExists;
}
}
auto newUser =
std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
userData.clientID, userData.userID);
// std::lock_guard<std::mutex> lock(this->mutex);
this->accounts.insertItem(newUser);
return AddUserResponse::UserAdded;
}
} // namespace chatterino
#include "providers/twitch/TwitchAccountManager.hpp"
#include "common/Common.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchCommon.hpp"
namespace chatterino {
TwitchAccountManager::TwitchAccountManager()
: anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
{
this->currentUserChanged.connect([this] {
auto currentUser = this->getCurrent();
currentUser->loadIgnores();
});
this->accounts.itemRemoved.connect([this](const auto &acc) { //
this->removeUser(acc.item.get());
});
}
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
{
if (!this->currentUser_)
{
return this->anonymousUser_;
}
return this->currentUser_;
}
std::vector<QString> TwitchAccountManager::getUsernames() const
{
std::vector<QString> userNames;
std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts)
{
userNames.push_back(user->getUserName());
}
return userNames;
}
std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
const QString &username) const
{
std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts)
{
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0)
{
return user;
}
}
return nullptr;
}
bool TwitchAccountManager::userExists(const QString &username) const
{
return this->findUserByUsername(username) != nullptr;
}
void TwitchAccountManager::reloadUsers()
{
auto keys = pajlada::Settings::SettingManager::getObjectKeys("/accounts");
UserData userData;
bool listUpdated = false;
for (const auto &uid : keys)
{
if (uid == "current")
{
continue;
}
auto username = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/username");
auto userID = pajlada::Settings::Setting<QString>::get("/accounts/" +
uid + "/userID");
auto clientID = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/clientID");
auto oauthToken = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/oauthToken");
if (username.isEmpty() || userID.isEmpty() || clientID.isEmpty() ||
oauthToken.isEmpty())
{
continue;
}
userData.username = username.trimmed();
userData.userID = userID.trimmed();
userData.clientID = clientID.trimmed();
userData.oauthToken = oauthToken.trimmed();
switch (this->addUser(userData))
{
case AddUserResponse::UserAlreadyExists:
{
log("User {} already exists", userData.username);
// Do nothing
}
break;
case AddUserResponse::UserValuesUpdated:
{
log("User {} already exists, and values updated!",
userData.username);
if (userData.username == this->getCurrent()->getUserName())
{
log("It was the current user, so we need to reconnect "
"stuff!");
this->currentUserChanged.invoke();
}
}
break;
case AddUserResponse::UserAdded:
{
log("Added user {}", userData.username);
listUpdated = true;
}
break;
}
}
if (listUpdated)
{
this->userListUpdated.invoke();
}
}
void TwitchAccountManager::load()
{
this->reloadUsers();
this->currentUsername.connect([this](const QString &newUsername) {
auto user = this->findUserByUsername(newUsername);
if (user)
{
log("[AccountManager:currentUsernameChanged] User successfully "
"updated to {}",
newUsername);
this->currentUser_ = user;
}
else
{
log("[AccountManager:currentUsernameChanged] User successfully "
"updated to anonymous");
this->currentUser_ = this->anonymousUser_;
}
this->currentUserChanged.invoke();
});
}
bool TwitchAccountManager::isLoggedIn() const
{
if (!this->currentUser_)
{
return false;
}
// Once `TwitchAccount` class has a way to check, we should also return
// false if the credentials are incorrect
return !this->currentUser_->isAnon();
}
bool TwitchAccountManager::removeUser(TwitchAccount *account)
{
auto userID(account->getUserId());
if (!userID.isEmpty())
{
pajlada::Settings::SettingManager::removeSetting(
fS("/accounts/uid{}", userID));
}
if (account->getUserName() == this->currentUsername)
{
// The user that was removed is the current user, log into the anonymous
// user
this->currentUsername = "";
}
this->userListUpdated.invoke();
return true;
}
TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
const TwitchAccountManager::UserData &userData)
{
auto previousUser = this->findUserByUsername(userData.username);
if (previousUser)
{
bool userUpdated = false;
if (previousUser->setOAuthClient(userData.clientID))
{
userUpdated = true;
}
if (previousUser->setOAuthToken(userData.oauthToken))
{
userUpdated = true;
}
if (userUpdated)
{
return AddUserResponse::UserValuesUpdated;
}
else
{
return AddUserResponse::UserAlreadyExists;
}
}
auto newUser =
std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
userData.clientID, userData.userID);
// std::lock_guard<std::mutex> lock(this->mutex);
this->accounts.insertItem(newUser);
return AddUserResponse::UserAdded;
}
} // namespace chatterino
+75 -75
View File
@@ -1,75 +1,75 @@
#pragma once
#include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "util/SharedPtrElementLess.hpp"
#include <mutex>
#include <vector>
//
// Warning: This class is not supposed to be created directly.
// Get yourself an instance from our friends over at
// AccountManager.hpp
//
namespace chatterino {
class TwitchAccount;
class AccountController;
class TwitchAccountManager
{
TwitchAccountManager();
public:
struct UserData {
QString username;
QString userID;
QString clientID;
QString oauthToken;
};
// Returns the current twitchUsers, or the anonymous user if we're not
// currently logged in
std::shared_ptr<TwitchAccount> getCurrent();
std::vector<QString> getUsernames() const;
std::shared_ptr<TwitchAccount> findUserByUsername(
const QString &username) const;
bool userExists(const QString &username) const;
void reloadUsers();
void load();
bool isLoggedIn() const;
pajlada::Settings::Setting<QString> currentUsername{"/accounts/current",
""};
pajlada::Signals::NoArgSignal currentUserChanged;
pajlada::Signals::NoArgSignal userListUpdated;
SortedSignalVector<std::shared_ptr<TwitchAccount>,
SharedPtrElementLess<TwitchAccount>>
accounts;
private:
enum class AddUserResponse {
UserAlreadyExists,
UserValuesUpdated,
UserAdded,
};
AddUserResponse addUser(const UserData &data);
bool removeUser(TwitchAccount *account);
std::shared_ptr<TwitchAccount> currentUser_;
std::shared_ptr<TwitchAccount> anonymousUser_;
mutable std::mutex mutex_;
friend class AccountController;
};
} // namespace chatterino
#pragma once
#include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "util/SharedPtrElementLess.hpp"
#include <mutex>
#include <vector>
//
// Warning: This class is not supposed to be created directly.
// Get yourself an instance from our friends over at
// AccountManager.hpp
//
namespace chatterino {
class TwitchAccount;
class AccountController;
class TwitchAccountManager
{
TwitchAccountManager();
public:
struct UserData {
QString username;
QString userID;
QString clientID;
QString oauthToken;
};
// Returns the current twitchUsers, or the anonymous user if we're not
// currently logged in
std::shared_ptr<TwitchAccount> getCurrent();
std::vector<QString> getUsernames() const;
std::shared_ptr<TwitchAccount> findUserByUsername(
const QString &username) const;
bool userExists(const QString &username) const;
void reloadUsers();
void load();
bool isLoggedIn() const;
pajlada::Settings::Setting<QString> currentUsername{"/accounts/current",
""};
pajlada::Signals::NoArgSignal currentUserChanged;
pajlada::Signals::NoArgSignal userListUpdated;
SortedSignalVector<std::shared_ptr<TwitchAccount>,
SharedPtrElementLess<TwitchAccount>>
accounts;
private:
enum class AddUserResponse {
UserAlreadyExists,
UserValuesUpdated,
UserAdded,
};
AddUserResponse addUser(const UserData &data);
bool removeUser(TwitchAccount *account);
std::shared_ptr<TwitchAccount> currentUser_;
std::shared_ptr<TwitchAccount> anonymousUser_;
mutable std::mutex mutex_;
friend class AccountController;
};
} // namespace chatterino
+2 -2
View File
@@ -16,7 +16,7 @@ void TwitchApi::findUserId(const QString user,
QString requestUrl("https://api.twitch.tv/kraken/users?login=" + user);
NetworkRequest(requestUrl)
.authorizeTwitchV5(getDefaultClientID())
.timeout(30000)
.onSuccess([successCallback](auto result) mutable -> Outcome {
@@ -65,7 +65,7 @@ void TwitchApi::findUserName(const QString userid,
QString requestUrl("https://api.twitch.tv/kraken/users/" + userid);
NetworkRequest(requestUrl)
.authorizeTwitchV5(getDefaultClientID())
.timeout(30000)
.onSuccess([successCallback](auto result) mutable -> Outcome {
+1 -1
View File
@@ -18,7 +18,7 @@ void TwitchBadges::loadTwitchBadges()
"https://badges.twitch.tv/v1/badges/global/display?language=en");
NetworkRequest(url)
.onSuccess([this](auto result) -> Outcome {
auto root = result.parseJson();
auto badgeSets = this->badgeSets_.access();
+49 -20
View File
@@ -88,7 +88,6 @@ TwitchChannel::TwitchChannel(const QString &name,
, globalFfz_(ffz)
, bttvEmotes_(std::make_shared<EmoteMap>())
, ffzEmotes_(std::make_shared<EmoteMap>())
, ffzCustomModBadge_(name)
, mod_(false)
{
log("[TwitchChannel:{}] Opened", name);
@@ -139,7 +138,6 @@ void TwitchChannel::initialize()
{
this->refreshChatters();
this->refreshBadges();
this->ffzCustomModBadge_.loadCustomModBadge();
}
bool TwitchChannel::isEmpty() const
@@ -165,10 +163,17 @@ void TwitchChannel::refreshBTTVChannelEmotes()
void TwitchChannel::refreshFFZChannelEmotes()
{
FfzEmotes::loadChannel(
this->roomId(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
this->roomId(),
[this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
if (auto shared = weak.lock())
this->ffzEmotes_.set(
std::make_shared<EmoteMap>(std::move(emoteMap)));
},
[this, weak = weakOf<Channel>(this)](auto &&modBadge) {
if (auto shared = weak.lock())
{
this->ffzCustomModBadge_.set(std::move(modBadge));
}
});
}
@@ -685,22 +690,23 @@ void TwitchChannel::refreshBadges()
void TwitchChannel::refreshCheerEmotes()
{
/*auto url = Url{"https://api.twitch.tv/kraken/bits/actions?channel_id=" +
this->getRoomId()};
auto request = NetworkRequest::twitchRequest(url.string);
request.setCaller(QThread::currentThread());
request.onSuccess(
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
QString url("https://api.twitch.tv/kraken/bits/actions?channel_id=" +
this->roomId());
NetworkRequest::twitchRequest(url)
.onSuccess([this,
weak = weakOf<Channel>(this)](auto result) -> Outcome {
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
std::vector<CheerEmoteSet> emoteSets;
for (auto &set : cheerEmoteSets) {
for (auto &set : cheerEmoteSets)
{
auto cheerEmoteSet = CheerEmoteSet();
cheerEmoteSet.regex = QRegularExpression(
"^" + set.prefix.toLower() + "([1-9][0-9]*)$");
"^" + set.prefix + "([1-9][0-9]*)$",
QRegularExpression::CaseInsensitiveOption);
for (auto &tier : set.tiers) {
for (auto &tier : set.tiers)
{
CheerEmote cheerEmote;
cheerEmote.color = QColor(tier.color);
@@ -733,7 +739,7 @@ void TwitchChannel::refreshCheerEmotes()
std::sort(cheerEmoteSet.cheerEmotes.begin(),
cheerEmoteSet.cheerEmotes.end(),
[](const auto &lhs, const auto &rhs) {
return lhs.minBits < rhs.minBits; //
return lhs.minBits > rhs.minBits;
});
emoteSets.emplace_back(cheerEmoteSet);
@@ -741,10 +747,8 @@ void TwitchChannel::refreshCheerEmotes()
*this->cheerEmoteSets_.access() = std::move(emoteSets);
return Success;
});
request.execute();
*/
})
.execute();
}
boost::optional<EmotePtr> TwitchChannel::twitchBadge(
@@ -765,9 +769,34 @@ boost::optional<EmotePtr> TwitchChannel::twitchBadge(
boost::optional<EmotePtr> TwitchChannel::ffzCustomModBadge() const
{
if (auto badge = this->ffzCustomModBadge_.badge())
return badge;
return this->ffzCustomModBadge_.get();
}
boost::optional<CheerEmote> TwitchChannel::cheerEmote(const QString &string)
{
auto sets = this->cheerEmoteSets_.access();
for (const auto &set : *sets)
{
auto match = set.regex.match(string);
if (!match.hasMatch())
{
continue;
}
QString amount = match.captured(1);
bool ok = false;
int bitAmount = amount.toInt(&ok);
if (!ok)
{
log("Error parsing bit amount in cheerEmote");
}
for (const auto &emote : set.cheerEmotes)
{
if (bitAmount >= emote.minBits)
{
return emote;
}
}
}
return boost::none;
}
+5 -2
View File
@@ -6,7 +6,7 @@
#include "common/ChannelChatters.hpp"
#include "common/Outcome.hpp"
#include "common/UniqueAccess.hpp"
#include "providers/ffz/FfzModBadge.hpp"
#include "common/UsernameSet.hpp"
#include "providers/twitch/TwitchEmotes.hpp"
#include <rapidjson/document.h>
@@ -96,6 +96,9 @@ public:
boost::optional<EmotePtr> twitchBadge(const QString &set,
const QString &version) const;
// Cheers
boost::optional<CheerEmote> cheerEmote(const QString &string);
// Signals
pajlada::Signals::NoArgSignal roomIdChanged;
pajlada::Signals::NoArgSignal userStateChanged;
@@ -145,13 +148,13 @@ protected:
FfzEmotes &globalFfz_;
Atomic<std::shared_ptr<const EmoteMap>> bttvEmotes_;
Atomic<std::shared_ptr<const EmoteMap>> ffzEmotes_;
Atomic<boost::optional<EmotePtr>> ffzCustomModBadge_;
private:
// Badges
UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>>
badgeSets_; // "subscribers": { "0": ... "3": ... "6": ...
UniqueAccess<std::vector<CheerEmoteSet>> cheerEmoteSets_;
FfzModBadge ffzCustomModBadge_;
bool mod_ = false;
bool vip_ = false;
+373 -373
View File
@@ -1,373 +1,373 @@
#include "TwitchIrcServer.hpp"
#include "Application.hpp"
#include "common/Common.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/ChatroomChannel.hpp"
#include "providers/twitch/IrcMessageHandler.hpp"
#include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchHelpers.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "util/PostToThread.hpp"
#include <IrcCommand>
#include <cassert>
// using namespace Communi;
using namespace std::chrono_literals;
namespace chatterino {
namespace {
bool isChatroom(const QString &channel)
{
if (channel.left(10) == "chatrooms:")
{
auto reflist = channel.splitRef(':');
if (reflist.size() == 3)
{
return true;
}
}
return false;
}
} // namespace
TwitchIrcServer::TwitchIrcServer()
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
{
this->pubsub = new PubSub;
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
// this->connect(); },
// this->signalHolder_,
// false);
}
void TwitchIrcServer::initialize(Settings &settings, Paths &paths)
{
getApp()->accounts->twitch.currentUserChanged.connect(
[this]() { postToThread([this] { this->connect(); }); });
this->twitchBadges.loadTwitchBadges();
this->bttv.loadEmotes();
this->ffz.loadEmotes();
}
void TwitchIrcServer::initializeConnection(IrcConnection *connection,
ConnectionType type)
{
std::shared_ptr<TwitchAccount> account =
getApp()->accounts->twitch.getCurrent();
qDebug() << "logging in as" << account->getUserName();
QString username = account->getUserName();
QString oauthToken = account->getOAuthToken();
if (!oauthToken.startsWith("oauth:"))
{
oauthToken.prepend("oauth:");
}
connection->setUserName(username);
connection->setNickName(username);
connection->setRealName(username);
if (!account->isAnon())
{
connection->setPassword(oauthToken);
}
connection->setSecure(true);
// https://dev.twitch.tv/docs/irc/guide/#connecting-to-twitch-irc
// SSL disabled: irc://irc.chat.twitch.tv:6667
// SSL enabled: irc://irc.chat.twitch.tv:6697
connection->setHost("irc.chat.twitch.tv");
connection->setPort(6697);
this->open(type);
}
std::shared_ptr<Channel> TwitchIrcServer::createChannel(
const QString &channelName)
{
std::shared_ptr<TwitchChannel> channel;
if (isChatroom(channelName))
{
channel = std::static_pointer_cast<TwitchChannel>(
std::shared_ptr<ChatroomChannel>(new ChatroomChannel(
channelName, this->twitchBadges, this->bttv, this->ffz)));
}
else
{
channel = std::shared_ptr<TwitchChannel>(new TwitchChannel(
channelName, this->twitchBadges, this->bttv, this->ffz));
}
channel->initialize();
channel->sendMessageSignal.connect(
[this, channel = channel.get()](auto &chan, auto &msg, bool &sent) {
this->onMessageSendRequested(channel, msg, sent);
});
return std::shared_ptr<Channel>(channel);
}
void TwitchIrcServer::privateMessageReceived(
Communi::IrcPrivateMessage *message)
{
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
}
void TwitchIrcServer::readConnectionMessageReceived(
Communi::IrcMessage *message)
{
AbstractIrcServer::readConnectionMessageReceived(message);
if (message->type() == Communi::IrcMessage::Type::Private)
{
// We already have a handler for private messages
return;
}
const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance();
// Below commands enabled through the twitch.tv/membership CAP REQ
if (command == "MODE")
{
handler.handleModeMessage(message);
}
else if (command == "JOIN")
{
handler.handleJoinMessage(message);
}
else if (command == "PART")
{
handler.handlePartMessage(message);
}
}
void TwitchIrcServer::writeConnectionMessageReceived(
Communi::IrcMessage *message)
{
const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance();
// Below commands enabled through the twitch.tv/commands CAP REQ
if (command == "USERSTATE")
{
handler.handleUserStateMessage(message);
}
else if (command == "WHISPER")
{
handler.handleWhisperMessage(message);
}
else if (command == "USERNOTICE")
{
handler.handleUserNoticeMessage(message, *this);
}
else if (command == "ROOMSTATE")
{
handler.handleRoomStateMessage(message);
}
else if (command == "CLEARCHAT")
{
handler.handleClearChatMessage(message);
}
else if (command == "CLEARMSG")
{
handler.handleClearMessageMessage(message);
}
else if (command == "NOTICE")
{
handler.handleNoticeMessage(
static_cast<Communi::IrcNoticeMessage *>(message));
}
}
void TwitchIrcServer::onReadConnected(IrcConnection *connection)
{
AbstractIrcServer::onReadConnected(connection);
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
// twitch.tv/membership enables the JOIN/PART/MODE/NAMES commands. See https://dev.twitch.tv/docs/irc/membership/
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/membership");
}
void TwitchIrcServer::onWriteConnected(IrcConnection *connection)
{
AbstractIrcServer::onWriteConnected(connection);
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
// twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands/
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands");
}
std::shared_ptr<Channel> TwitchIrcServer::getCustomChannel(
const QString &channelName)
{
if (channelName == "/whispers")
{
return this->whispersChannel;
}
if (channelName == "/mentions")
{
return this->mentionsChannel;
}
if (channelName == "$$$")
{
static auto channel =
std::make_shared<Channel>("$$$", chatterino::Channel::Type::Misc);
static auto getTimer = [&] {
for (auto i = 0; i < 1000; i++)
{
channel->addMessage(makeSystemMessage(QString::number(i + 1)));
}
auto timer = new QTimer;
QObject::connect(timer, &QTimer::timeout, [] {
channel->addMessage(
makeSystemMessage(QTime::currentTime().toString()));
});
timer->start(500);
return timer;
}();
return channel;
}
return nullptr;
}
void TwitchIrcServer::forEachChannelAndSpecialChannels(
std::function<void(ChannelPtr)> func)
{
this->forEachChannel(func);
func(this->whispersChannel);
func(this->mentionsChannel);
}
std::shared_ptr<Channel> TwitchIrcServer::getChannelOrEmptyByID(
const QString &channelId)
{
std::lock_guard<std::mutex> lock(this->channelMutex);
for (const auto &weakChannel : this->channels)
{
auto channel = weakChannel.lock();
if (!channel)
continue;
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel);
if (!twitchChannel)
continue;
if (twitchChannel->roomId() == channelId &&
twitchChannel->getName().splitRef(":").size() < 3)
{
return twitchChannel;
}
}
return Channel::getEmpty();
}
QString TwitchIrcServer::cleanChannelName(const QString &dirtyChannelName)
{
if (dirtyChannelName.startsWith('#'))
return dirtyChannelName.mid(1).toLower();
else
return dirtyChannelName.toLower();
}
bool TwitchIrcServer::hasSeparateWriteConnection() const
{
return true;
// return getSettings()->twitchSeperateWriteConnection;
}
void TwitchIrcServer::onMessageSendRequested(TwitchChannel *channel,
const QString &message, bool &sent)
{
sent = false;
{
std::lock_guard<std::mutex> guard(this->lastMessageMutex_);
// std::queue<std::chrono::steady_clock::time_point>
auto &lastMessage = channel->hasHighRateLimit()
? this->lastMessageMod_
: this->lastMessagePleb_;
size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19;
auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms);
auto now = std::chrono::steady_clock::now();
// check if you are sending messages too fast
if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now)
{
if (this->lastErrorTimeSpeed_ + 30s < now)
{
auto errorMessage =
makeSystemMessage("sending messages too fast");
channel->addMessage(errorMessage);
this->lastErrorTimeSpeed_ = now;
}
return;
}
// remove messages older than 30 seconds
while (!lastMessage.empty() && lastMessage.front() + 32s < now)
{
lastMessage.pop();
}
// check if you are sending too many messages
if (lastMessage.size() >= maxMessageCount)
{
if (this->lastErrorTimeAmount_ + 30s < now)
{
auto errorMessage =
makeSystemMessage("sending too many messages");
channel->addMessage(errorMessage);
this->lastErrorTimeAmount_ = now;
}
return;
}
lastMessage.push(now);
}
this->sendMessage(channel->getName(), message);
sent = true;
}
const BttvEmotes &TwitchIrcServer::getBttvEmotes() const
{
return this->bttv;
}
const FfzEmotes &TwitchIrcServer::getFfzEmotes() const
{
return this->ffz;
}
} // namespace chatterino
#include "TwitchIrcServer.hpp"
#include "Application.hpp"
#include "common/Common.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/ChatroomChannel.hpp"
#include "providers/twitch/IrcMessageHandler.hpp"
#include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchHelpers.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "util/PostToThread.hpp"
#include <IrcCommand>
#include <cassert>
// using namespace Communi;
using namespace std::chrono_literals;
namespace chatterino {
namespace {
bool isChatroom(const QString &channel)
{
if (channel.left(10) == "chatrooms:")
{
auto reflist = channel.splitRef(':');
if (reflist.size() == 3)
{
return true;
}
}
return false;
}
} // namespace
TwitchIrcServer::TwitchIrcServer()
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
{
this->pubsub = new PubSub;
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
// this->connect(); },
// this->signalHolder_,
// false);
}
void TwitchIrcServer::initialize(Settings &settings, Paths &paths)
{
getApp()->accounts->twitch.currentUserChanged.connect(
[this]() { postToThread([this] { this->connect(); }); });
this->twitchBadges.loadTwitchBadges();
this->bttv.loadEmotes();
this->ffz.loadEmotes();
}
void TwitchIrcServer::initializeConnection(IrcConnection *connection,
ConnectionType type)
{
std::shared_ptr<TwitchAccount> account =
getApp()->accounts->twitch.getCurrent();
qDebug() << "logging in as" << account->getUserName();
QString username = account->getUserName();
QString oauthToken = account->getOAuthToken();
if (!oauthToken.startsWith("oauth:"))
{
oauthToken.prepend("oauth:");
}
connection->setUserName(username);
connection->setNickName(username);
connection->setRealName(username);
if (!account->isAnon())
{
connection->setPassword(oauthToken);
}
connection->setSecure(true);
// https://dev.twitch.tv/docs/irc/guide/#connecting-to-twitch-irc
// SSL disabled: irc://irc.chat.twitch.tv:6667
// SSL enabled: irc://irc.chat.twitch.tv:6697
connection->setHost("irc.chat.twitch.tv");
connection->setPort(6697);
this->open(type);
}
std::shared_ptr<Channel> TwitchIrcServer::createChannel(
const QString &channelName)
{
std::shared_ptr<TwitchChannel> channel;
if (isChatroom(channelName))
{
channel = std::static_pointer_cast<TwitchChannel>(
std::shared_ptr<ChatroomChannel>(new ChatroomChannel(
channelName, this->twitchBadges, this->bttv, this->ffz)));
}
else
{
channel = std::shared_ptr<TwitchChannel>(new TwitchChannel(
channelName, this->twitchBadges, this->bttv, this->ffz));
}
channel->initialize();
channel->sendMessageSignal.connect(
[this, channel = channel.get()](auto &chan, auto &msg, bool &sent) {
this->onMessageSendRequested(channel, msg, sent);
});
return std::shared_ptr<Channel>(channel);
}
void TwitchIrcServer::privateMessageReceived(
Communi::IrcPrivateMessage *message)
{
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
}
void TwitchIrcServer::readConnectionMessageReceived(
Communi::IrcMessage *message)
{
AbstractIrcServer::readConnectionMessageReceived(message);
if (message->type() == Communi::IrcMessage::Type::Private)
{
// We already have a handler for private messages
return;
}
const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance();
// Below commands enabled through the twitch.tv/membership CAP REQ
if (command == "MODE")
{
handler.handleModeMessage(message);
}
else if (command == "JOIN")
{
handler.handleJoinMessage(message);
}
else if (command == "PART")
{
handler.handlePartMessage(message);
}
}
void TwitchIrcServer::writeConnectionMessageReceived(
Communi::IrcMessage *message)
{
const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance();
// Below commands enabled through the twitch.tv/commands CAP REQ
if (command == "USERSTATE")
{
handler.handleUserStateMessage(message);
}
else if (command == "WHISPER")
{
handler.handleWhisperMessage(message);
}
else if (command == "USERNOTICE")
{
handler.handleUserNoticeMessage(message, *this);
}
else if (command == "ROOMSTATE")
{
handler.handleRoomStateMessage(message);
}
else if (command == "CLEARCHAT")
{
handler.handleClearChatMessage(message);
}
else if (command == "CLEARMSG")
{
handler.handleClearMessageMessage(message);
}
else if (command == "NOTICE")
{
handler.handleNoticeMessage(
static_cast<Communi::IrcNoticeMessage *>(message));
}
}
void TwitchIrcServer::onReadConnected(IrcConnection *connection)
{
AbstractIrcServer::onReadConnected(connection);
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
// twitch.tv/membership enables the JOIN/PART/MODE/NAMES commands. See https://dev.twitch.tv/docs/irc/membership/
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/membership");
}
void TwitchIrcServer::onWriteConnected(IrcConnection *connection)
{
AbstractIrcServer::onWriteConnected(connection);
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
// twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands/
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands");
}
std::shared_ptr<Channel> TwitchIrcServer::getCustomChannel(
const QString &channelName)
{
if (channelName == "/whispers")
{
return this->whispersChannel;
}
if (channelName == "/mentions")
{
return this->mentionsChannel;
}
if (channelName == "$$$")
{
static auto channel =
std::make_shared<Channel>("$$$", chatterino::Channel::Type::Misc);
static auto getTimer = [&] {
for (auto i = 0; i < 1000; i++)
{
channel->addMessage(makeSystemMessage(QString::number(i + 1)));
}
auto timer = new QTimer;
QObject::connect(timer, &QTimer::timeout, [] {
channel->addMessage(
makeSystemMessage(QTime::currentTime().toString()));
});
timer->start(500);
return timer;
}();
return channel;
}
return nullptr;
}
void TwitchIrcServer::forEachChannelAndSpecialChannels(
std::function<void(ChannelPtr)> func)
{
this->forEachChannel(func);
func(this->whispersChannel);
func(this->mentionsChannel);
}
std::shared_ptr<Channel> TwitchIrcServer::getChannelOrEmptyByID(
const QString &channelId)
{
std::lock_guard<std::mutex> lock(this->channelMutex);
for (const auto &weakChannel : this->channels)
{
auto channel = weakChannel.lock();
if (!channel)
continue;
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel);
if (!twitchChannel)
continue;
if (twitchChannel->roomId() == channelId &&
twitchChannel->getName().splitRef(":").size() < 3)
{
return twitchChannel;
}
}
return Channel::getEmpty();
}
QString TwitchIrcServer::cleanChannelName(const QString &dirtyChannelName)
{
if (dirtyChannelName.startsWith('#'))
return dirtyChannelName.mid(1).toLower();
else
return dirtyChannelName.toLower();
}
bool TwitchIrcServer::hasSeparateWriteConnection() const
{
return true;
// return getSettings()->twitchSeperateWriteConnection;
}
void TwitchIrcServer::onMessageSendRequested(TwitchChannel *channel,
const QString &message, bool &sent)
{
sent = false;
{
std::lock_guard<std::mutex> guard(this->lastMessageMutex_);
// std::queue<std::chrono::steady_clock::time_point>
auto &lastMessage = channel->hasHighRateLimit()
? this->lastMessageMod_
: this->lastMessagePleb_;
size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19;
auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms);
auto now = std::chrono::steady_clock::now();
// check if you are sending messages too fast
if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now)
{
if (this->lastErrorTimeSpeed_ + 30s < now)
{
auto errorMessage =
makeSystemMessage("sending messages too fast");
channel->addMessage(errorMessage);
this->lastErrorTimeSpeed_ = now;
}
return;
}
// remove messages older than 30 seconds
while (!lastMessage.empty() && lastMessage.front() + 32s < now)
{
lastMessage.pop();
}
// check if you are sending too many messages
if (lastMessage.size() >= maxMessageCount)
{
if (this->lastErrorTimeAmount_ + 30s < now)
{
auto errorMessage =
makeSystemMessage("sending too many messages");
channel->addMessage(errorMessage);
this->lastErrorTimeAmount_ = now;
}
return;
}
lastMessage.push(now);
}
this->sendMessage(channel->getName(), message);
sent = true;
}
const BttvEmotes &TwitchIrcServer::getBttvEmotes() const
{
return this->bttv;
}
const FfzEmotes &TwitchIrcServer::getFfzEmotes() const
{
return this->ffz;
}
} // namespace chatterino
+85 -85
View File
@@ -1,85 +1,85 @@
#pragma once
#include "common/Atomic.hpp"
#include "common/Channel.hpp"
#include "common/Singleton.hpp"
#include "pajlada/signals/signalholder.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/ffz/FfzEmotes.hpp"
#include "providers/irc/AbstractIrcServer.hpp"
#include "providers/twitch/TwitchBadges.hpp"
#include <chrono>
#include <memory>
#include <queue>
namespace chatterino {
class Settings;
class Paths;
class PubSub;
class TwitchChannel;
class TwitchIrcServer final : public AbstractIrcServer, public Singleton
{
public:
TwitchIrcServer();
virtual ~TwitchIrcServer() override = default;
virtual void initialize(Settings &settings, Paths &paths) override;
void forEachChannelAndSpecialChannels(std::function<void(ChannelPtr)> func);
std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID);
Atomic<QString> lastUserThatWhisperedMe;
const ChannelPtr whispersChannel;
const ChannelPtr mentionsChannel;
IndirectChannel watchingChannel;
PubSub *pubsub;
const BttvEmotes &getBttvEmotes() const;
const FfzEmotes &getFfzEmotes() const;
protected:
virtual void initializeConnection(IrcConnection *connection,
ConnectionType type) override;
virtual std::shared_ptr<Channel> createChannel(
const QString &channelName) override;
virtual void privateMessageReceived(
Communi::IrcPrivateMessage *message) override;
virtual void readConnectionMessageReceived(
Communi::IrcMessage *message) override;
virtual void writeConnectionMessageReceived(
Communi::IrcMessage *message) override;
virtual void onReadConnected(IrcConnection *connection) override;
virtual void onWriteConnected(IrcConnection *connection) override;
virtual std::shared_ptr<Channel> getCustomChannel(
const QString &channelname) override;
virtual QString cleanChannelName(const QString &dirtyChannelName) override;
virtual bool hasSeparateWriteConnection() const override;
private:
void onMessageSendRequested(TwitchChannel *channel, const QString &message,
bool &sent);
std::mutex lastMessageMutex_;
std::queue<std::chrono::steady_clock::time_point> lastMessagePleb_;
std::queue<std::chrono::steady_clock::time_point> lastMessageMod_;
std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
std::chrono::steady_clock::time_point lastErrorTimeAmount_;
TwitchBadges twitchBadges;
BttvEmotes bttv;
FfzEmotes ffz;
pajlada::Signals::SignalHolder signalHolder_;
};
} // namespace chatterino
#pragma once
#include "common/Atomic.hpp"
#include "common/Channel.hpp"
#include "common/Singleton.hpp"
#include "pajlada/signals/signalholder.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/ffz/FfzEmotes.hpp"
#include "providers/irc/AbstractIrcServer.hpp"
#include "providers/twitch/TwitchBadges.hpp"
#include <chrono>
#include <memory>
#include <queue>
namespace chatterino {
class Settings;
class Paths;
class PubSub;
class TwitchChannel;
class TwitchIrcServer final : public AbstractIrcServer, public Singleton
{
public:
TwitchIrcServer();
virtual ~TwitchIrcServer() override = default;
virtual void initialize(Settings &settings, Paths &paths) override;
void forEachChannelAndSpecialChannels(std::function<void(ChannelPtr)> func);
std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID);
Atomic<QString> lastUserThatWhisperedMe;
const ChannelPtr whispersChannel;
const ChannelPtr mentionsChannel;
IndirectChannel watchingChannel;
PubSub *pubsub;
const BttvEmotes &getBttvEmotes() const;
const FfzEmotes &getFfzEmotes() const;
protected:
virtual void initializeConnection(IrcConnection *connection,
ConnectionType type) override;
virtual std::shared_ptr<Channel> createChannel(
const QString &channelName) override;
virtual void privateMessageReceived(
Communi::IrcPrivateMessage *message) override;
virtual void readConnectionMessageReceived(
Communi::IrcMessage *message) override;
virtual void writeConnectionMessageReceived(
Communi::IrcMessage *message) override;
virtual void onReadConnected(IrcConnection *connection) override;
virtual void onWriteConnected(IrcConnection *connection) override;
virtual std::shared_ptr<Channel> getCustomChannel(
const QString &channelname) override;
virtual QString cleanChannelName(const QString &dirtyChannelName) override;
virtual bool hasSeparateWriteConnection() const override;
private:
void onMessageSendRequested(TwitchChannel *channel, const QString &message,
bool &sent);
std::mutex lastMessageMutex_;
std::queue<std::chrono::steady_clock::time_point> lastMessagePleb_;
std::queue<std::chrono::steady_clock::time_point> lastMessageMod_;
std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
std::chrono::steady_clock::time_point lastErrorTimeAmount_;
TwitchBadges twitchBadges;
BttvEmotes bttv;
FfzEmotes ffz;
pajlada::Signals::SignalHolder signalHolder_;
};
} // namespace chatterino
+66 -68
View File
@@ -143,9 +143,21 @@ bool TwitchMessageBuilder::isIgnored() const
return false;
}
inline QMediaPlayer *getPlayer()
{
if (isGuiThread())
{
static auto player = new QMediaPlayer;
return player;
}
else
{
return nullptr;
}
}
void TwitchMessageBuilder::triggerHighlights()
{
static auto player = new QMediaPlayer;
static QUrl currentPlayerUrl;
if (this->historicalMessage_)
@@ -165,21 +177,24 @@ void TwitchMessageBuilder::triggerHighlights()
if (this->highlightSound_ && resolveFocus)
{
// update the media player url if necessary
QUrl highlightSoundUrl =
getSettings()->customHighlightSound
? QUrl::fromLocalFile(
getSettings()->pathHighlightSound.getValue())
: QUrl("qrc:/sounds/ping2.wav");
if (currentPlayerUrl != highlightSoundUrl)
if (auto player = getPlayer())
{
player->setMedia(highlightSoundUrl);
// update the media player url if necessary
QUrl highlightSoundUrl =
getSettings()->customHighlightSound
? QUrl::fromLocalFile(
getSettings()->pathHighlightSound.getValue())
: QUrl("qrc:/sounds/ping2.wav");
currentPlayerUrl = highlightSoundUrl;
if (currentPlayerUrl != highlightSoundUrl)
{
player->setMedia(highlightSoundUrl);
currentPlayerUrl = highlightSoundUrl;
}
player->play();
}
player->play();
}
if (this->highlightAlert_)
@@ -274,7 +289,7 @@ MessagePtr TwitchMessageBuilder::build()
if (iterator != this->tags.end())
{
this->hasBits_ = true;
// bits = iterator.value().toString();
this->bits = iterator.value().toString();
}
// twitch emotes
@@ -340,7 +355,7 @@ void TwitchMessageBuilder::addWords(
auto i = int();
auto currentTwitchEmote = twitchEmotes.begin();
for (const auto &word : words)
for (auto word : words)
{
// check if it's a twitch emote twitch emote
while (currentTwitchEmote != twitchEmotes.end() &&
@@ -361,9 +376,19 @@ void TwitchMessageBuilder::addWords(
MessageElementFlag::TwitchEmote);
i += word.length() + 1;
int len = std::get<2>(*currentTwitchEmote).string.length();
currentTwitchEmote++;
continue;
if (len < word.length())
{
word = word.mid(len);
this->message().elements.back()->setTrailingSpace(false);
}
else
{
continue;
}
}
// split words
@@ -952,7 +977,7 @@ void TwitchMessageBuilder::parseHighlights()
{
HighlightPhrase selfHighlight(
currentUsername, getSettings()->enableSelfHighlightTaskbar,
getSettings()->enableSelfHighlightSound, false);
getSettings()->enableSelfHighlightSound, false, false);
activeHighlights.emplace_back(std::move(selfHighlight));
}
@@ -1171,7 +1196,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
{
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
{
this->emplace<BadgeElement>(
this->emplace<ModBadgeElement>(
customModBadge.get(),
MessageElementFlag::BadgeChannelAuthority)
->setTooltip((*customModBadge)->tooltip.string);
@@ -1285,56 +1310,29 @@ void TwitchMessageBuilder::appendChatterinoBadges()
}
}
Outcome TwitchMessageBuilder::tryParseCheermote(const QString & /*string*/)
Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
{
// auto app = getApp();
//// Try to parse custom cheermotes
// const auto &channelResources = app->resources->channels[this->roomID_];
// if (channelResources.loaded) {
// for (const auto &cheermoteSet : channelResources.cheermoteSets) {
// auto match = cheermoteSet.regex.match(string);
// if (!match.hasMatch()) {
// continue;
// }
// QString amount = match.captured(1);
// bool ok = false;
// int numBits = amount.toInt(&ok);
// if (!ok) {
// Log("Error parsing bit amount in tryParseCheermote");
// return Failure;
// }
// auto savedIt = cheermoteSet.cheermotes.end();
// // Fetch cheermote that matches our numBits
// for (auto it = cheermoteSet.cheermotes.begin(); it !=
// cheermoteSet.cheermotes.end();
// ++it) {
// if (numBits >= it->minBits) {
// savedIt = it;
// } else {
// break;
// }
// }
// if (savedIt == cheermoteSet.cheermotes.end()) {
// Log("Error getting a cheermote from a cheermote set for the
// bit amount {}",
// numBits);
// return Failure;
// }
// const auto &cheermote = *savedIt;
// this->emplace<EmoteElement>(cheermote.animatedEmote,
// MessageElementFlag::BitsAnimated);
// this->emplace<TextElement>(amount, MessageElementFlag::Text,
// cheermote.color);
// return Success;
// }
//}
return Failure;
auto cheerOpt = this->twitchChannel->cheerEmote(string);
if (!cheerOpt)
{
return Failure;
}
auto &cheerEmote = *cheerOpt;
if (cheerEmote.staticEmote)
{
this->emplace<EmoteElement>(cheerEmote.staticEmote,
MessageElementFlag::BitsStatic);
}
if (cheerEmote.animatedEmote)
{
this->emplace<EmoteElement>(cheerEmote.animatedEmote,
MessageElementFlag::BitsAnimated);
}
if (cheerEmote.color != QColor())
{
this->emplace<TextElement>(this->bits, MessageElementFlag::BitsAmount,
cheerEmote.color);
}
return Success;
}
} // namespace chatterino
@@ -78,6 +78,7 @@ private:
QString roomID_;
bool hasBits_ = false;
QString bits;
bool historicalMessage_ = false;
QString userId_;