2017-09-16 00:05:06 +02:00
|
|
|
#pragma once
|
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
#include "Application.hpp"
|
2018-08-11 22:23:06 +02:00
|
|
|
#include "common/Aliases.hpp"
|
2018-08-11 17:15:17 +02:00
|
|
|
#include "common/Atomic.hpp"
|
2018-06-26 15:33:51 +02:00
|
|
|
#include "common/Channel.hpp"
|
2019-09-17 12:10:38 +02:00
|
|
|
#include "common/ChannelChatters.hpp"
|
2021-05-24 12:13:59 +02:00
|
|
|
#include "common/ChatterSet.hpp"
|
2018-08-11 22:23:06 +02:00
|
|
|
#include "common/Outcome.hpp"
|
2018-07-15 20:28:54 +02:00
|
|
|
#include "common/UniqueAccess.hpp"
|
2022-07-31 12:45:25 +02:00
|
|
|
#include "messages/MessageThread.hpp"
|
2020-08-08 15:37:22 +02:00
|
|
|
#include "providers/twitch/ChannelPointReward.hpp"
|
2018-08-13 13:54:39 +02:00
|
|
|
#include "providers/twitch/TwitchEmotes.hpp"
|
2020-03-14 12:13:57 +01:00
|
|
|
#include "providers/twitch/api/Helix.hpp"
|
2021-07-11 11:12:49 +02:00
|
|
|
#include "util/QStringHash.hpp"
|
2018-04-15 15:09:31 +02:00
|
|
|
|
2018-08-13 13:54:39 +02:00
|
|
|
#include <QColor>
|
2021-03-13 17:54:34 +01:00
|
|
|
#include <QElapsedTimer>
|
2018-08-13 13:54:39 +02:00
|
|
|
#include <QRegularExpression>
|
2018-08-11 22:23:06 +02:00
|
|
|
#include <boost/optional.hpp>
|
2022-05-07 17:22:39 +02:00
|
|
|
#include <boost/signals2.hpp>
|
2018-08-11 22:23:06 +02:00
|
|
|
#include <pajlada/signals/signalholder.hpp>
|
2020-03-14 12:13:57 +01:00
|
|
|
|
2022-08-06 18:18:34 +02:00
|
|
|
#include <atomic>
|
2020-03-14 12:13:57 +01:00
|
|
|
#include <mutex>
|
2018-08-02 14:23:27 +02:00
|
|
|
#include <unordered_map>
|
2018-03-30 15:05:33 +02:00
|
|
|
|
2017-09-16 00:05:06 +02:00
|
|
|
namespace chatterino {
|
2018-03-24 12:02:07 +01:00
|
|
|
|
2022-01-11 01:18:02 +01:00
|
|
|
// This is to make sure that combined emoji go through properly, see
|
|
|
|
// https://github.com/Chatterino/chatterino2/issues/3384 and
|
|
|
|
// https://mm2pl.github.io/emoji_rfc.pdf for more details
|
|
|
|
const QString ZERO_WIDTH_JOINER = QString(QChar(0x200D));
|
|
|
|
|
|
|
|
// Here be MSVC: Do NOT replace with "\U" literal, it will fail silently.
|
|
|
|
namespace {
|
|
|
|
const QChar ESCAPE_TAG_CHARS[2] = {QChar::highSurrogate(0xE0002),
|
|
|
|
QChar::lowSurrogate(0xE0002)};
|
|
|
|
}
|
|
|
|
const QString ESCAPE_TAG = QString(ESCAPE_TAG_CHARS, 2);
|
|
|
|
|
|
|
|
const static QRegularExpression COMBINED_FIXER(
|
|
|
|
QString("(?<!%1)%1").arg(ESCAPE_TAG),
|
|
|
|
QRegularExpression::UseUnicodePropertiesOption);
|
|
|
|
|
2018-08-29 19:25:37 +02:00
|
|
|
enum class HighlightState;
|
|
|
|
|
2018-08-11 22:23:06 +02:00
|
|
|
struct Emote;
|
|
|
|
using EmotePtr = std::shared_ptr<const Emote>;
|
|
|
|
class EmoteMap;
|
|
|
|
|
2018-08-14 17:45:17 +02:00
|
|
|
class TwitchBadges;
|
2018-08-12 00:01:37 +02:00
|
|
|
class FfzEmotes;
|
|
|
|
class BttvEmotes;
|
|
|
|
|
2019-09-15 13:02:02 +02:00
|
|
|
class TwitchIrcServer;
|
2018-03-24 12:02:07 +01:00
|
|
|
|
2021-12-19 15:57:56 +01:00
|
|
|
class TwitchChannel : public Channel, public ChannelChatters
|
2017-09-16 00:05:06 +02:00
|
|
|
{
|
|
|
|
public:
|
2018-03-30 15:05:33 +02:00
|
|
|
struct StreamStatus {
|
|
|
|
bool live = false;
|
2018-04-08 15:14:14 +02:00
|
|
|
bool rerun = false;
|
2018-03-30 15:05:33 +02:00
|
|
|
unsigned viewerCount = 0;
|
|
|
|
QString title;
|
|
|
|
QString game;
|
2020-03-14 12:13:57 +01:00
|
|
|
QString gameId;
|
2018-03-30 15:05:33 +02:00
|
|
|
QString uptime;
|
2018-05-26 16:31:43 +02:00
|
|
|
QString streamType;
|
2018-03-30 15:05:33 +02:00
|
|
|
};
|
|
|
|
|
2018-05-24 08:58:34 +02:00
|
|
|
struct RoomModes {
|
|
|
|
bool submode = false;
|
|
|
|
bool r9k = false;
|
|
|
|
bool emoteOnly = false;
|
2019-08-25 19:08:04 +02:00
|
|
|
int followerOnly = -1;
|
2018-05-24 08:58:34 +02:00
|
|
|
int slowMode = 0;
|
|
|
|
};
|
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
explicit TwitchChannel(const QString &channelName);
|
|
|
|
|
2018-08-13 13:54:39 +02:00
|
|
|
void initialize();
|
2018-01-17 17:17:26 +01:00
|
|
|
|
2018-07-15 20:28:54 +02:00
|
|
|
// Channel methods
|
|
|
|
virtual bool isEmpty() const override;
|
|
|
|
virtual bool canSendMessage() const override;
|
|
|
|
virtual void sendMessage(const QString &message) override;
|
2022-07-31 12:45:25 +02:00
|
|
|
virtual void sendReply(const QString &message, const QString &replyId);
|
2018-07-15 20:28:54 +02:00
|
|
|
virtual bool isMod() const override;
|
2019-10-07 20:31:34 +02:00
|
|
|
bool isVip() const;
|
2019-04-13 15:26:47 +02:00
|
|
|
bool isStaff() const;
|
2018-07-15 20:28:54 +02:00
|
|
|
virtual bool isBroadcaster() const override;
|
2019-04-13 15:26:47 +02:00
|
|
|
virtual bool hasHighRateLimit() const override;
|
2019-09-18 08:05:51 +02:00
|
|
|
virtual bool canReconnect() const override;
|
|
|
|
virtual void reconnect() override;
|
2019-10-12 15:09:12 +02:00
|
|
|
void refreshTitle();
|
2021-01-17 14:47:34 +01:00
|
|
|
void createClip();
|
2018-07-14 14:24:18 +02:00
|
|
|
|
2018-08-12 00:01:37 +02:00
|
|
|
// Data
|
|
|
|
const QString &subscriptionUrl();
|
|
|
|
const QString &channelUrl();
|
|
|
|
const QString &popoutPlayerUrl();
|
2021-01-16 13:58:11 +01:00
|
|
|
int chatterCount();
|
2018-10-13 14:20:06 +02:00
|
|
|
virtual bool isLive() const override;
|
2018-08-11 17:15:17 +02:00
|
|
|
QString roomId() const;
|
2021-05-01 17:19:41 +02:00
|
|
|
SharedAccessGuard<const RoomModes> accessRoomModes() const;
|
|
|
|
SharedAccessGuard<const StreamStatus> accessStreamStatus() const;
|
2017-12-23 23:24:35 +01:00
|
|
|
|
2018-08-12 00:01:37 +02:00
|
|
|
// Emotes
|
2018-08-11 17:15:17 +02:00
|
|
|
boost::optional<EmotePtr> bttvEmote(const EmoteName &name) const;
|
|
|
|
boost::optional<EmotePtr> ffzEmote(const EmoteName &name) const;
|
|
|
|
std::shared_ptr<const EmoteMap> bttvEmotes() const;
|
|
|
|
std::shared_ptr<const EmoteMap> ffzEmotes() const;
|
2017-09-16 00:05:06 +02:00
|
|
|
|
2020-05-16 12:43:44 +02:00
|
|
|
virtual void refreshBTTVChannelEmotes(bool manualRefresh);
|
|
|
|
virtual void refreshFFZChannelEmotes(bool manualRefresh);
|
2017-09-16 00:05:06 +02:00
|
|
|
|
2018-08-13 13:54:39 +02:00
|
|
|
// Badges
|
2018-10-25 21:53:03 +02:00
|
|
|
boost::optional<EmotePtr> ffzCustomModBadge() const;
|
2021-04-17 14:42:30 +02:00
|
|
|
boost::optional<EmotePtr> ffzCustomVipBadge() const;
|
2018-08-13 13:54:39 +02:00
|
|
|
boost::optional<EmotePtr> twitchBadge(const QString &set,
|
|
|
|
const QString &version) const;
|
2018-08-02 14:23:27 +02:00
|
|
|
|
2019-09-08 12:45:25 +02:00
|
|
|
// Cheers
|
2019-09-08 18:01:38 +02:00
|
|
|
boost::optional<CheerEmote> cheerEmote(const QString &string);
|
2019-09-08 12:45:25 +02:00
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
// Replies
|
|
|
|
/**
|
|
|
|
* Stores the given thread in this channel.
|
|
|
|
*
|
|
|
|
* Note: This method not take ownership of the MessageThread; this
|
|
|
|
* TwitchChannel instance will store a weak_ptr to the thread.
|
|
|
|
*/
|
|
|
|
void addReplyThread(const std::shared_ptr<MessageThread> &thread);
|
|
|
|
const std::unordered_map<QString, std::weak_ptr<MessageThread>> &threads()
|
|
|
|
const;
|
|
|
|
|
2018-07-15 20:28:54 +02:00
|
|
|
// Signals
|
|
|
|
pajlada::Signals::NoArgSignal roomIdChanged;
|
2018-04-03 02:55:32 +02:00
|
|
|
pajlada::Signals::NoArgSignal userStateChanged;
|
2018-08-13 13:54:39 +02:00
|
|
|
pajlada::Signals::NoArgSignal liveStatusChanged;
|
2018-05-24 08:58:34 +02:00
|
|
|
pajlada::Signals::NoArgSignal roomModesChanged;
|
2017-12-28 00:03:52 +01:00
|
|
|
|
2020-08-08 15:37:22 +02:00
|
|
|
// Channel point rewards
|
|
|
|
pajlada::Signals::SelfDisconnectingSignal<ChannelPointReward>
|
|
|
|
channelPointRewardAdded;
|
|
|
|
void addChannelPointReward(const ChannelPointReward &reward);
|
|
|
|
bool isChannelPointRewardKnown(const QString &rewardId);
|
|
|
|
boost::optional<ChannelPointReward> channelPointReward(
|
|
|
|
const QString &rewardId) const;
|
|
|
|
|
2017-09-16 00:05:06 +02:00
|
|
|
private:
|
2018-07-15 20:28:54 +02:00
|
|
|
struct NameOptions {
|
|
|
|
QString displayName;
|
|
|
|
QString localizedName;
|
2020-12-06 14:07:33 +01:00
|
|
|
} nameOptions;
|
2018-07-15 20:28:54 +02:00
|
|
|
|
2019-02-26 21:00:57 +01:00
|
|
|
private:
|
2018-07-15 20:28:54 +02:00
|
|
|
// Methods
|
2017-11-04 14:57:29 +01:00
|
|
|
void refreshLiveStatus();
|
2020-03-14 12:13:57 +01:00
|
|
|
void parseLiveStatus(bool live, const HelixStream &stream);
|
2022-05-07 17:22:39 +02:00
|
|
|
void refreshPubSub();
|
2018-08-13 13:54:39 +02:00
|
|
|
void refreshChatters();
|
|
|
|
void refreshBadges();
|
|
|
|
void refreshCheerEmotes();
|
2018-07-14 14:24:18 +02:00
|
|
|
void loadRecentMessages();
|
2022-08-06 18:18:34 +02:00
|
|
|
void loadRecentMessagesReconnect();
|
2020-12-06 14:07:33 +01:00
|
|
|
void fetchDisplayName();
|
2022-07-31 12:45:25 +02:00
|
|
|
void cleanUpReplyThreads();
|
|
|
|
void showLoginMessage();
|
2018-07-14 14:24:18 +02:00
|
|
|
|
|
|
|
void setLive(bool newLiveStatus);
|
2018-08-13 13:54:39 +02:00
|
|
|
void setMod(bool value);
|
2019-04-13 15:26:47 +02:00
|
|
|
void setVIP(bool value);
|
|
|
|
void setStaff(bool value);
|
2018-08-13 13:54:39 +02:00
|
|
|
void setRoomId(const QString &id);
|
|
|
|
void setRoomModes(const RoomModes &roomModes_);
|
2020-12-06 14:07:33 +01:00
|
|
|
void setDisplayName(const QString &name);
|
|
|
|
void setLocalizedName(const QString &name);
|
|
|
|
|
|
|
|
const QString &getDisplayName() const override;
|
|
|
|
const QString &getLocalizedName() const override;
|
2017-11-04 14:57:29 +01:00
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
QString prepareMessage(const QString &message) const;
|
|
|
|
|
2018-08-12 00:01:37 +02:00
|
|
|
// Data
|
2018-07-15 20:28:54 +02:00
|
|
|
const QString subscriptionUrl_;
|
|
|
|
const QString channelUrl_;
|
|
|
|
const QString popoutPlayerUrl_;
|
2021-01-16 13:58:11 +01:00
|
|
|
int chatterCount_;
|
2018-07-15 20:28:54 +02:00
|
|
|
UniqueAccess<StreamStatus> streamStatus_;
|
|
|
|
UniqueAccess<RoomModes> roomModes_;
|
2022-08-06 18:18:34 +02:00
|
|
|
std::atomic_flag loadingRecentMessages_ = ATOMIC_FLAG_INIT;
|
2022-07-31 12:45:25 +02:00
|
|
|
std::unordered_map<QString, std::weak_ptr<MessageThread>> threads_;
|
2018-03-30 15:05:33 +02:00
|
|
|
|
2019-02-26 21:00:57 +01:00
|
|
|
protected:
|
2018-08-11 17:15:17 +02:00
|
|
|
Atomic<std::shared_ptr<const EmoteMap>> bttvEmotes_;
|
|
|
|
Atomic<std::shared_ptr<const EmoteMap>> ffzEmotes_;
|
2019-09-08 11:36:35 +02:00
|
|
|
Atomic<boost::optional<EmotePtr>> ffzCustomModBadge_;
|
2021-04-17 14:42:30 +02:00
|
|
|
Atomic<boost::optional<EmotePtr>> ffzCustomVipBadge_;
|
2018-04-15 15:09:31 +02:00
|
|
|
|
2019-02-26 21:00:57 +01:00
|
|
|
private:
|
2018-08-13 13:54:39 +02:00
|
|
|
// Badges
|
|
|
|
UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>>
|
|
|
|
badgeSets_; // "subscribers": { "0": ... "3": ... "6": ...
|
|
|
|
UniqueAccess<std::vector<CheerEmoteSet>> cheerEmoteSets_;
|
2020-08-08 15:37:22 +02:00
|
|
|
UniqueAccess<std::map<QString, ChannelPointReward>> channelPointRewards_;
|
2018-04-15 15:09:31 +02:00
|
|
|
|
2018-07-06 19:23:47 +02:00
|
|
|
bool mod_ = false;
|
2019-04-13 15:26:47 +02:00
|
|
|
bool vip_ = false;
|
|
|
|
bool staff_ = false;
|
2018-08-10 19:00:14 +02:00
|
|
|
UniqueAccess<QString> roomID_;
|
2018-01-17 17:17:26 +01:00
|
|
|
|
2018-07-15 20:28:54 +02:00
|
|
|
// --
|
|
|
|
QString lastSentMessage_;
|
|
|
|
QObject lifetimeGuard_;
|
2018-07-14 14:24:18 +02:00
|
|
|
QTimer chattersListTimer_;
|
2022-07-31 12:45:25 +02:00
|
|
|
QTimer threadClearTimer_;
|
2021-03-13 17:54:34 +01:00
|
|
|
QElapsedTimer titleRefreshedTimer_;
|
|
|
|
QElapsedTimer clipCreationTimer_;
|
2021-01-17 14:47:34 +01:00
|
|
|
bool isClipCreationInProgress{false};
|
2019-10-12 15:09:12 +02:00
|
|
|
|
2021-12-19 15:57:56 +01:00
|
|
|
pajlada::Signals::SignalHolder signalHolder_;
|
2022-05-07 17:22:39 +02:00
|
|
|
std::vector<boost::signals2::scoped_connection> bSignals_;
|
2021-12-19 15:57:56 +01:00
|
|
|
|
2019-09-15 13:02:02 +02:00
|
|
|
friend class TwitchIrcServer;
|
2018-08-13 13:54:39 +02:00
|
|
|
friend class TwitchMessageBuilder;
|
|
|
|
friend class IrcMessageHandler;
|
2018-02-05 15:11:50 +01:00
|
|
|
};
|
2018-03-24 12:02:07 +01:00
|
|
|
|
2017-11-04 14:57:29 +01:00
|
|
|
} // namespace chatterino
|