mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Compare commits
13 commits
731418dbb8
...
617d47b7e7
Author | SHA1 | Date | |
---|---|---|---|
617d47b7e7 | |||
a9b446ffc7 | |||
021f1a0bfc | |||
e87302ca2d | |||
64ccef94d1 | |||
6836105408 | |||
ee80dd5d27 | |||
5638346d7c | |||
c64523f23c | |||
cde4144e30 | |||
c4faca50e0 | |||
7d0c473828 | |||
f4036b269b |
|
@ -7,7 +7,7 @@
|
||||||
- Major: Improve high-DPI support on Windows. (#4868, #5391, #5664, #5666)
|
- Major: Improve high-DPI support on Windows. (#4868, #5391, #5664, #5666)
|
||||||
- Major: Added transparent overlay window (default keybind: <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>N</kbd>). (#4746, #5643, #5659)
|
- Major: Added transparent overlay window (default keybind: <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>N</kbd>). (#4746, #5643, #5659)
|
||||||
- Minor: Removed the Ctrl+Shift+L hotkey for toggling the "live only" tab visibility state. (#5530)
|
- Minor: Removed the Ctrl+Shift+L hotkey for toggling the "live only" tab visibility state. (#5530)
|
||||||
- Minor: Add support for Shared Chat messages. Shared chat messages can be filtered with the `flags.shared` filter variable, or with search using `is:shared`. Some messages like subscriptions are filtered on purpose to avoid confusion for the broadcaster. If you have both channels participating in Shared Chat open, only one of the message triggering your highlight will trigger. (#5606, #5625)
|
- Minor: Add support for Shared Chat messages. Shared chat messages can be filtered with the `flags.shared` filter variable, or with search using `is:shared`. Some messages like subscriptions are filtered on purpose to avoid confusion for the broadcaster. If you have both channels participating in Shared Chat open, only one of the message triggering your highlight will trigger. (#5606, #5625, #5661)
|
||||||
- Minor: Moved tab visibility control to a submenu, without any toggle actions. (#5530)
|
- Minor: Moved tab visibility control to a submenu, without any toggle actions. (#5530)
|
||||||
- Minor: Add option to customise Moderation buttons with images. (#5369)
|
- Minor: Add option to customise Moderation buttons with images. (#5369)
|
||||||
- Minor: Colored usernames now update on the fly when changing the "Color @usernames" setting. (#5300)
|
- Minor: Colored usernames now update on the fly when changing the "Color @usernames" setting. (#5300)
|
||||||
|
|
|
@ -71,6 +71,17 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void remove(const key_t &key)
|
||||||
|
{
|
||||||
|
auto it = _cache_items_map.find(key);
|
||||||
|
if (it == _cache_items_map.end())
|
||||||
|
{
|
||||||
|
throw std::range_error("There is no such key in cache");
|
||||||
|
}
|
||||||
|
_cache_items_list.erase(it->second);
|
||||||
|
_cache_items_map.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
const value_t &get(const key_t &key)
|
const value_t &get(const key_t &key)
|
||||||
{
|
{
|
||||||
auto it = _cache_items_map.find(key);
|
auto it = _cache_items_map.find(key);
|
||||||
|
|
|
@ -26,6 +26,7 @@ public:
|
||||||
, mentionsChannel(std::shared_ptr<Channel>(new MockChannel("forsen3")))
|
, mentionsChannel(std::shared_ptr<Channel>(new MockChannel("forsen3")))
|
||||||
, liveChannel(std::shared_ptr<Channel>(new MockChannel("forsen")))
|
, liveChannel(std::shared_ptr<Channel>(new MockChannel("forsen")))
|
||||||
, automodChannel(std::shared_ptr<Channel>(new MockChannel("forsen2")))
|
, automodChannel(std::shared_ptr<Channel>(new MockChannel("forsen2")))
|
||||||
|
, channelNamesById_(1)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +50,18 @@ public:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<QString> getOrPopulateChannelCache(
|
||||||
|
const QString &channelId) override
|
||||||
|
{
|
||||||
|
if (channelId == "11148817")
|
||||||
|
return "pajlada";
|
||||||
|
if (channelId == "141981764")
|
||||||
|
return "twitchdev";
|
||||||
|
if (channelId == "1025594235")
|
||||||
|
return "shared_chat_test_01";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void addFakeMessage(const QString &data) override
|
void addFakeMessage(const QString &data) override
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -148,6 +161,7 @@ public:
|
||||||
ChannelPtr mentionsChannel;
|
ChannelPtr mentionsChannel;
|
||||||
ChannelPtr liveChannel;
|
ChannelPtr liveChannel;
|
||||||
ChannelPtr automodChannel;
|
ChannelPtr automodChannel;
|
||||||
|
UniqueAccess<cache::lru_cache<QString, QString>> channelNamesById_;
|
||||||
QString lastUserThatWhisperedMe{"forsen"};
|
QString lastUserThatWhisperedMe{"forsen"};
|
||||||
|
|
||||||
std::unordered_map<QString, std::weak_ptr<Channel>> mockChannels;
|
std::unordered_map<QString, std::weak_ptr<Channel>> mockChannels;
|
||||||
|
|
BIN
resources/twitch/sharedChat.png
Normal file
BIN
resources/twitch/sharedChat.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1 KiB |
|
@ -380,6 +380,17 @@ EmotePtr makeAutoModBadge()
|
||||||
Url{"https://dashboard.twitch.tv/settings/moderation/automod"}});
|
Url{"https://dashboard.twitch.tv/settings/moderation/automod"}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EmotePtr makeSharedChatBadge(const QString &sourceName)
|
||||||
|
{
|
||||||
|
return std::make_shared<Emote>(
|
||||||
|
Emote{"SharedChat_" + sourceName,
|
||||||
|
ImageSet{Image::fromResourcePixmap(
|
||||||
|
getResources().twitch.sharedChat, 0.25)},
|
||||||
|
Tooltip{"Shared Message" +
|
||||||
|
(sourceName.isEmpty() ? "" : " from " + sourceName)},
|
||||||
|
Url{"https://link.twitch.tv/SharedChatViewer"}});
|
||||||
|
}
|
||||||
|
|
||||||
std::tuple<std::optional<EmotePtr>, MessageElementFlags, bool> parseEmote(
|
std::tuple<std::optional<EmotePtr>, MessageElementFlags, bool> parseEmote(
|
||||||
TwitchChannel *twitchChannel, const EmoteName &name)
|
TwitchChannel *twitchChannel, const EmoteName &name)
|
||||||
{
|
{
|
||||||
|
@ -2746,6 +2757,33 @@ void MessageBuilder::appendTwitchBadges(const QVariantMap &tags,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this->message().flags.has(MessageFlag::SharedMessage))
|
||||||
|
{
|
||||||
|
const QString sourceId = tags["source-room-id"].toString();
|
||||||
|
std::optional<QString> sourceName;
|
||||||
|
if (twitchChannel->roomId() == sourceId)
|
||||||
|
{
|
||||||
|
sourceName = twitchChannel->getName();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sourceName =
|
||||||
|
getApp()->getTwitch()->getOrPopulateChannelCache(sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceName.has_value())
|
||||||
|
{
|
||||||
|
const auto &name = sourceName.value();
|
||||||
|
auto *badge = this->emplace<BadgeElement>(
|
||||||
|
makeSharedChatBadge(name),
|
||||||
|
MessageElementFlag::BadgeSharedChannel);
|
||||||
|
if (!name.isEmpty())
|
||||||
|
{
|
||||||
|
badge->setLink({Link::UserInfo, name});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto badgeInfos = parseBadgeInfoTag(tags);
|
auto badgeInfos = parseBadgeInfoTag(tags);
|
||||||
auto badges = parseBadgeTag(tags);
|
auto badges = parseBadgeTag(tags);
|
||||||
appendBadges(this, badges, badgeInfos, twitchChannel);
|
appendBadges(this, badges, badgeInfos, twitchChannel);
|
||||||
|
|
|
@ -66,6 +66,10 @@ enum class MessageElementFlag : int64_t {
|
||||||
BitsStatic = (1LL << 11),
|
BitsStatic = (1LL << 11),
|
||||||
BitsAnimated = (1LL << 12),
|
BitsAnimated = (1LL << 12),
|
||||||
|
|
||||||
|
// Slot 0: Twitch
|
||||||
|
// - Shared Channel indicator badge
|
||||||
|
BadgeSharedChannel = (1LL << 37),
|
||||||
|
|
||||||
// Slot 1: Twitch
|
// Slot 1: Twitch
|
||||||
// - Staff badge
|
// - Staff badge
|
||||||
// - Admin badge
|
// - Admin badge
|
||||||
|
@ -119,7 +123,7 @@ enum class MessageElementFlag : int64_t {
|
||||||
|
|
||||||
Badges = BadgeGlobalAuthority | BadgePredictions | BadgeChannelAuthority |
|
Badges = BadgeGlobalAuthority | BadgePredictions | BadgeChannelAuthority |
|
||||||
BadgeSubscription | BadgeVanity | BadgeChatterino | BadgeSevenTV |
|
BadgeSubscription | BadgeVanity | BadgeChatterino | BadgeSevenTV |
|
||||||
BadgeFfz,
|
BadgeFfz | BadgeSharedChannel,
|
||||||
|
|
||||||
ChannelName = (1LL << 20),
|
ChannelName = (1LL << 20),
|
||||||
|
|
||||||
|
|
|
@ -153,6 +153,7 @@ TwitchIrcServer::TwitchIrcServer()
|
||||||
, liveChannel(new Channel("/live", Channel::Type::TwitchLive))
|
, liveChannel(new Channel("/live", Channel::Type::TwitchLive))
|
||||||
, automodChannel(new Channel("/automod", Channel::Type::TwitchAutomod))
|
, automodChannel(new Channel("/automod", Channel::Type::TwitchAutomod))
|
||||||
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
||||||
|
, channelNamesById_(512)
|
||||||
{
|
{
|
||||||
// Initialize the connections
|
// Initialize the connections
|
||||||
// XXX: don't create write connection if there is no separate write connection.
|
// XXX: don't create write connection if there is no separate write connection.
|
||||||
|
@ -1128,6 +1129,38 @@ std::shared_ptr<Channel> TwitchIrcServer::getChannelOrEmptyByID(
|
||||||
return Channel::getEmpty();
|
return Channel::getEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<QString> TwitchIrcServer::getOrPopulateChannelCache(
|
||||||
|
const QString &channelId)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const auto cache = this->channelNamesById_.access();
|
||||||
|
if (cache->exists(channelId))
|
||||||
|
{
|
||||||
|
return cache->get(channelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent multiple helix requests for single user
|
||||||
|
cache->put(channelId, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
getHelix()->getUserById(
|
||||||
|
channelId,
|
||||||
|
[this](const HelixUser &user) {
|
||||||
|
const auto cache = this->channelNamesById_.access();
|
||||||
|
cache->put(user.id, user.login);
|
||||||
|
},
|
||||||
|
[this, &channelId] {
|
||||||
|
const auto cache = this->channelNamesById_.access();
|
||||||
|
if (cache->exists(channelId) && cache->get(channelId).isEmpty())
|
||||||
|
{
|
||||||
|
// invalidate cache so another helix request can be attempted
|
||||||
|
cache->remove(channelId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
QString TwitchIrcServer::cleanChannelName(const QString &dirtyChannelName)
|
QString TwitchIrcServer::cleanChannelName(const QString &dirtyChannelName)
|
||||||
{
|
{
|
||||||
if (dirtyChannelName.startsWith('#'))
|
if (dirtyChannelName.startsWith('#'))
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
#include "common/Atomic.hpp"
|
#include "common/Atomic.hpp"
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
|
#include "common/UniqueAccess.hpp"
|
||||||
#include "providers/irc/IrcConnection2.hpp"
|
#include "providers/irc/IrcConnection2.hpp"
|
||||||
#include "util/RatelimitBucket.hpp"
|
#include "util/RatelimitBucket.hpp"
|
||||||
|
|
||||||
#include <IrcMessage>
|
#include <IrcMessage>
|
||||||
|
#include <lrucache/lrucache.hpp>
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
#include <pajlada/signals/signalholder.hpp>
|
#include <pajlada/signals/signalholder.hpp>
|
||||||
|
|
||||||
|
@ -43,6 +45,9 @@ public:
|
||||||
virtual ChannelPtr getOrAddChannel(const QString &dirtyChannelName) = 0;
|
virtual ChannelPtr getOrAddChannel(const QString &dirtyChannelName) = 0;
|
||||||
virtual ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName) = 0;
|
virtual ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName) = 0;
|
||||||
|
|
||||||
|
virtual std::optional<QString> getOrPopulateChannelCache(
|
||||||
|
const QString &channelId) = 0;
|
||||||
|
|
||||||
virtual void addFakeMessage(const QString &data) = 0;
|
virtual void addFakeMessage(const QString &data) = 0;
|
||||||
|
|
||||||
virtual void addGlobalSystemMessage(const QString &messageText) = 0;
|
virtual void addGlobalSystemMessage(const QString &messageText) = 0;
|
||||||
|
@ -95,6 +100,14 @@ public:
|
||||||
std::shared_ptr<Channel> getChannelOrEmptyByID(
|
std::shared_ptr<Channel> getChannelOrEmptyByID(
|
||||||
const QString &channelID) override;
|
const QString &channelID) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the channel login name associated with the passed ID,
|
||||||
|
* so that Shared Chat messages can provide source channel context.
|
||||||
|
* Can yield an empty string if a helix request is already in-flight.
|
||||||
|
*/
|
||||||
|
std::optional<QString> getOrPopulateChannelCache(
|
||||||
|
const QString &channelId) override;
|
||||||
|
|
||||||
void reloadAllBTTVChannelEmotes();
|
void reloadAllBTTVChannelEmotes();
|
||||||
void reloadAllFFZChannelEmotes();
|
void reloadAllFFZChannelEmotes();
|
||||||
void reloadAllSevenTVChannelEmotes();
|
void reloadAllSevenTVChannelEmotes();
|
||||||
|
@ -190,6 +203,9 @@ private:
|
||||||
// https://dev.twitch.tv/docs/irc/guide#rate-limits
|
// https://dev.twitch.tv/docs/irc/guide#rate-limits
|
||||||
QObjectPtr<RatelimitBucket> joinBucket_;
|
QObjectPtr<RatelimitBucket> joinBucket_;
|
||||||
|
|
||||||
|
// cached channel id => name for resolving Shared Chat members
|
||||||
|
UniqueAccess<cache::lru_cache<QString, QString>> channelNamesById_;
|
||||||
|
|
||||||
QTimer reconnectTimer_;
|
QTimer reconnectTimer_;
|
||||||
int falloffCounter_ = 1;
|
int falloffCounter_ = 1;
|
||||||
|
|
||||||
|
|
|
@ -195,6 +195,7 @@ void WindowManager::updateWordTypeMask()
|
||||||
flags.set(settings->animateEmotes ? MEF::BitsAnimated : MEF::BitsStatic);
|
flags.set(settings->animateEmotes ? MEF::BitsAnimated : MEF::BitsStatic);
|
||||||
|
|
||||||
// badges
|
// badges
|
||||||
|
flags.set(MEF::BadgeSharedChannel);
|
||||||
flags.set(settings->showBadgesGlobalAuthority ? MEF::BadgeGlobalAuthority
|
flags.set(settings->showBadgesGlobalAuthority ? MEF::BadgeGlobalAuthority
|
||||||
: MEF::None);
|
: MEF::None);
|
||||||
flags.set(settings->showBadgesPredictions ? MEF::BadgePredictions
|
flags.set(settings->showBadgesPredictions ? MEF::BadgePredictions
|
||||||
|
|
|
@ -64,6 +64,24 @@
|
||||||
"trailingSpace": true,
|
"trailingSpace": true,
|
||||||
"type": "TwitchModerationElement"
|
"type": "TwitchModerationElement"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"emote": {
|
||||||
|
"homePage": "https://link.twitch.tv/SharedChatViewer",
|
||||||
|
"images": {
|
||||||
|
"1x": ""
|
||||||
|
},
|
||||||
|
"name": "SharedChat_shared_chat_test_01",
|
||||||
|
"tooltip": "Shared Message from shared_chat_test_01"
|
||||||
|
},
|
||||||
|
"flags": "BadgeSharedChannel",
|
||||||
|
"link": {
|
||||||
|
"type": "UserInfo",
|
||||||
|
"value": "shared_chat_test_01"
|
||||||
|
},
|
||||||
|
"tooltip": "Shared Message from shared_chat_test_01",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "BadgeElement"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"emote": {
|
"emote": {
|
||||||
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",
|
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",
|
||||||
|
|
|
@ -64,6 +64,24 @@
|
||||||
"trailingSpace": true,
|
"trailingSpace": true,
|
||||||
"type": "TwitchModerationElement"
|
"type": "TwitchModerationElement"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"emote": {
|
||||||
|
"homePage": "https://link.twitch.tv/SharedChatViewer",
|
||||||
|
"images": {
|
||||||
|
"1x": ""
|
||||||
|
},
|
||||||
|
"name": "SharedChat_twitchdev",
|
||||||
|
"tooltip": "Shared Message from twitchdev"
|
||||||
|
},
|
||||||
|
"flags": "BadgeSharedChannel",
|
||||||
|
"link": {
|
||||||
|
"type": "UserInfo",
|
||||||
|
"value": "twitchdev"
|
||||||
|
},
|
||||||
|
"tooltip": "Shared Message from twitchdev",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "BadgeElement"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"color": "#ffff0000",
|
"color": "#ffff0000",
|
||||||
"flags": "Username",
|
"flags": "Username",
|
||||||
|
|
|
@ -64,6 +64,24 @@
|
||||||
"trailingSpace": true,
|
"trailingSpace": true,
|
||||||
"type": "TwitchModerationElement"
|
"type": "TwitchModerationElement"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"emote": {
|
||||||
|
"homePage": "https://link.twitch.tv/SharedChatViewer",
|
||||||
|
"images": {
|
||||||
|
"1x": ""
|
||||||
|
},
|
||||||
|
"name": "SharedChat_twitchdev",
|
||||||
|
"tooltip": "Shared Message from twitchdev"
|
||||||
|
},
|
||||||
|
"flags": "BadgeSharedChannel",
|
||||||
|
"link": {
|
||||||
|
"type": "UserInfo",
|
||||||
|
"value": "twitchdev"
|
||||||
|
},
|
||||||
|
"tooltip": "Shared Message from twitchdev",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "BadgeElement"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"emote": {
|
"emote": {
|
||||||
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",
|
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",
|
||||||
|
|
|
@ -64,6 +64,24 @@
|
||||||
"trailingSpace": true,
|
"trailingSpace": true,
|
||||||
"type": "TwitchModerationElement"
|
"type": "TwitchModerationElement"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"emote": {
|
||||||
|
"homePage": "https://link.twitch.tv/SharedChatViewer",
|
||||||
|
"images": {
|
||||||
|
"1x": ""
|
||||||
|
},
|
||||||
|
"name": "SharedChat_shared_chat_test_01",
|
||||||
|
"tooltip": "Shared Message from shared_chat_test_01"
|
||||||
|
},
|
||||||
|
"flags": "BadgeSharedChannel",
|
||||||
|
"link": {
|
||||||
|
"type": "UserInfo",
|
||||||
|
"value": "shared_chat_test_01"
|
||||||
|
},
|
||||||
|
"tooltip": "Shared Message from shared_chat_test_01",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "BadgeElement"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"emote": {
|
"emote": {
|
||||||
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",
|
"homePage": "https://www.twitch.tv/jobs?ref=chat_badge",
|
||||||
|
|
Loading…
Reference in a new issue