Merge remote-tracking branch 'origin/master' into zneix/feat/chatrooms-user-impl

This commit is contained in:
zneix 2022-05-31 16:48:55 +02:00
commit 1e0e2eb67f
No known key found for this signature in database
GPG key ID: 911916E0523B22F6
56 changed files with 1204 additions and 661 deletions

View file

@ -83,8 +83,10 @@ jobs:
- name: Test (Ubuntu)
if: startsWith(matrix.os, 'ubuntu')
run: |
docker pull kennethreitz/httpbin
docker pull ${{ env.TWITCH_PUBSUB_SERVER_IMAGE }}
docker run --network=host --detach ${{ env.TWITCH_PUBSUB_SERVER_IMAGE }}
docker run -p 9051:80 --detach kennethreitz/httpbin
./bin/chatterino-test --platform minimal
working-directory: build-test
shell: bash

View file

@ -4,9 +4,9 @@
This guide assumes you are on a 64-bit system. You might need to manually search out alternate download links should you desire to build Chatterino on a 32-bit system.
## Visual Studio 2019
## Visual Studio 2022
Download and install [Visual Studio 2019 Community](https://visualstudio.microsoft.com/downloads/). In the installer, select "Desktop development with C++" and "Universal Windows Platform development".
Download and install [Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/). In the installer, select "Desktop development with C++" and "Universal Windows Platform development".
Notes:
@ -20,14 +20,14 @@ Notes:
- Visit the downloads list on [SourceForge](https://sourceforge.net/projects/boost/files/boost-binaries/).
- Select the latest version from the list.
- Download the `.exe` file appropriate to your Visual Studio installation version and system bitness (choose `-64` for 64-bit systems).
Visual Studio versions map as follows: `14.2` in the filename corresponds to MSVC 2019, `14.1` to 2017, `14.0` to 2015. _Anything prior to Visual Studio 2015 is unsupported. Please upgrade should you have an older installation._
Visual Studio versions map as follows: `14.3` in the filename corresponds to MSVC 2022,`14.2` to 2019, `14.1` to 2017, `14.0` to 2015. _Anything prior to Visual Studio 2015 is unsupported. Please upgrade should you have an older installation._
**Convenience link for Visual Studio 2019: [boost_1_76_0-msvc-14.2-64.exe](https://sourceforge.net/projects/boost/files/boost-binaries/1.76.0/boost_1_76_0-msvc-14.2-64.exe/download)**
**Convenience link for Visual Studio 2022: [boost_1_79_0_b1-msvc-14.3-64.exe](https://sourceforge.net/projects/boost/files/boost-binaries/1.79.0_b1/boost_1_79_0_b1-msvc-14.3-64.exe/download)**
2. When prompted where to install Boost, set the location to `C:\local\boost`.
3. After the installation finishes, rename the `C:\local\boost\lib64-msvc-14.2` (or similar) directory to simply `lib` (`C:\local\boost\lib`).
3. After the installation finishes, rename the `C:\local\boost\boost_1_79_0_b1_rc1` (or similar) directory to simply `lib` (`C:\local\boost\lib`).
Note: This installation will take about 1.5 GB of disk space.
Note: This installation will take about 2.1 GB of disk space.
## OpenSSL
@ -84,7 +84,7 @@ Compiling with Breakpad support enables crash reports that can be of use for dev
1. Open the `chatterino.pro` file by double-clicking it, or by opening it via Qt Creator.
2. You will be presented with a screen that is titled "Configure Project". In this screen, you should have at least one option present ready to be configured, like this:
![Qt Create Configure Project screenshot](https://user-images.githubusercontent.com/41973452/159462759-470e5371-671e-478e-85ca-33452ca9bea3.png)
![Qt Create Configure Project screenshot](https://user-images.githubusercontent.com/69117321/169887645-2ae0871a-fe8a-4eb9-98db-7b996dea3a54.png)
3. Select the profile(s) you want to build with and click "Configure Project".
### How to run and produce builds

View file

@ -2,20 +2,25 @@
## Unversioned
- Major: Added multi-channel searching to search dialog via keyboard shortcut. [Ctrl+Shift+F by default] (#3694)
- Minor: Added `is:first-msg` search option. (#3700)
- Minor: Added quotation marks in the permitted/blocked Automod messages for clarity. (#3654)
- Minor: Adjust large stream thumbnail to 16:9 (#3655)
- Minor: Fixed being unable to load Twitch Usercards from the `/mentions` tab. (#3623)
- Minor: Add information about the user's operating system in the About page. (#3663)
- Bugfix: Connection to Twitch PubSub now recovers more reliably. (#3643, #3716)
- Minor: Added chatter count for each category in viewer list. (#3683)
- Minor: Added chatter count for each category in viewer list. (#3683, #3719)
- Minor: Sorted usernames in /vips message to be case-insensitive. (#3696)
- Minor: Added option to open a user's chat in a new tab from the usercard profile picture context menu. (#3625)
- Minor: Fixed tag parsing for consecutive escaped characters. (#3711)
- Minor: Prevent user from entering incorrect characters in Live Notifications channels list. (#3715, #3730)
- Minor: Fixed automod caught message notice appearing twice for mods. (#3717)
- Minor: Streamer mode now automatically detects if XSplit, PRISM Live Studio, Twitch Studio, or vMix are running. (#3740)
- Minor: Add scrollbar to `Select filters` dialog. (#3737)
- Minor: Added `/requests` command. Usage: `/requests [channel]`. Opens the channel points requests queue for the provided channel or the current channel if no input is provided. (#3746)
- Minor: Added ability to execute commands on chat messages using the message context menu. (#3738)
- Minor: Added ability to execute commands on chat messages using the message context menu. (#3738, #3765)
- Minor: Added `/copy` command. Usage: `/copy <text>`. Copies provided text to clipboard - can be useful with custom commands. (#3763)
- Bugfix: Fixed viewers list search not working when used before loading finishes. (#3774)
- Bugfix: Fixed live notifications for usernames containing uppercase characters. (#3646)
- Bugfix: Fixed live notifications not getting updated for closed streams going offline. (#3678)
- Bugfix: Fixed certain settings dialogs appearing behind the main window, when `Always on top` was used. (#3679)
@ -27,7 +32,7 @@
- Bugfix: Fixed viewer list not closing after pressing escape key. (#3734)
- Bugfix: Fixed links with no thumbnail having previous link's thumbnail. (#3720)
- Dev: Use Game Name returned by Get Streams instead of querying it from the Get Games API. (#3662)
- Dev: Batch checking live status for all channels after startup. (#3757, #3762)
- Dev: Batch checking live status for all channels after startup. (#3757, #3762, #3767)
## 2.3.5

View file

@ -3,6 +3,7 @@ project(chatterino-benchmark)
set(benchmark_SOURCES
${CMAKE_CURRENT_LIST_DIR}/src/main.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Emojis.cpp
${CMAKE_CURRENT_LIST_DIR}/src/FormatTime.cpp
# Add your new file above this line!
)

View file

@ -0,0 +1,38 @@
#include "util/FormatTime.hpp"
#include <benchmark/benchmark.h>
using namespace chatterino;
template <class... Args>
void BM_TimeFormatting(benchmark::State &state, Args &&...args)
{
auto args_tuple = std::make_tuple(std::move(args)...);
for (auto _ : state)
{
formatTime(std::get<0>(args_tuple));
}
}
BENCHMARK_CAPTURE(BM_TimeFormatting, 0, 0);
BENCHMARK_CAPTURE(BM_TimeFormatting, qs0, "0");
BENCHMARK_CAPTURE(BM_TimeFormatting, 1337, 1337);
BENCHMARK_CAPTURE(BM_TimeFormatting, qs1337, "1337");
BENCHMARK_CAPTURE(BM_TimeFormatting, 623452, 623452);
BENCHMARK_CAPTURE(BM_TimeFormatting, qs623452, "623452");
BENCHMARK_CAPTURE(BM_TimeFormatting, 8345, 8345);
BENCHMARK_CAPTURE(BM_TimeFormatting, qs8345, "8345");
BENCHMARK_CAPTURE(BM_TimeFormatting, 314034, 314034);
BENCHMARK_CAPTURE(BM_TimeFormatting, qs314034, "314034");
BENCHMARK_CAPTURE(BM_TimeFormatting, 27, 27);
BENCHMARK_CAPTURE(BM_TimeFormatting, qs27, "27");
BENCHMARK_CAPTURE(BM_TimeFormatting, 34589, 34589);
BENCHMARK_CAPTURE(BM_TimeFormatting, qs34589, "34589");
BENCHMARK_CAPTURE(BM_TimeFormatting, 3659, 3659);
BENCHMARK_CAPTURE(BM_TimeFormatting, qs3659, "3659");
BENCHMARK_CAPTURE(BM_TimeFormatting, 1045345, 1045345);
BENCHMARK_CAPTURE(BM_TimeFormatting, qs1045345, "1045345");
BENCHMARK_CAPTURE(BM_TimeFormatting, 86432, 86432);
BENCHMARK_CAPTURE(BM_TimeFormatting, qs86432, "86432");
BENCHMARK_CAPTURE(BM_TimeFormatting, qsempty, "");
BENCHMARK_CAPTURE(BM_TimeFormatting, qsinvalid, "asd");

View file

@ -264,6 +264,7 @@ SOURCES += \
src/util/NuulsUploader.cpp \
src/util/RapidjsonHelpers.cpp \
src/util/RatelimitBucket.cpp \
src/util/SampleData.cpp \
src/util/SplitCommand.cpp \
src/util/StreamerMode.cpp \
src/util/StreamLink.cpp \
@ -529,7 +530,6 @@ HEADERS += \
src/util/IncognitoBrowser.hpp \
src/util/InitUpdateButton.hpp \
src/util/IrcHelpers.hpp \
src/util/IsBigEndian.hpp \
src/util/LayoutCreator.hpp \
src/util/LayoutHelper.hpp \
src/util/NuulsUploader.hpp \
@ -539,13 +539,11 @@ HEADERS += \
src/util/QObjectRef.hpp \
src/util/QStringHash.hpp \
src/util/Qt.hpp \
src/util/rangealgorithm.hpp \
src/util/RapidjsonHelpers.hpp \
src/util/RapidJsonSerializeQString.hpp \
src/util/RatelimitBucket.hpp \
src/util/RemoveScrollAreaBackground.hpp \
src/util/SampleCheerMessages.hpp \
src/util/SampleLinks.hpp \
src/util/SampleData.hpp \
src/util/SharedPtrElementLess.hpp \
src/util/SplitCommand.hpp \
src/util/StandardItemHelper.hpp \

@ -1 +1 @@
Subproject commit 87190b811c5d6bb90f970902bcda28740f6c07d3
Subproject commit 6956c560330e21c7673ba8d5e43d53b71a7fce48

View file

@ -4,7 +4,7 @@ resources_header = \
resources_footer = \
''' </qresource>
</RCC>'''
</RCC>\n'''
header_header = \
'''#include <QPixmap>
@ -22,7 +22,7 @@ public:
header_footer = \
'''};
} // namespace chatterino'''
} // namespace chatterino\n'''
source_header = \
'''#include "ResourcesAutogen.hpp"
@ -36,4 +36,4 @@ Resources2::Resources2()
source_footer = \
'''}
} // namespace chatterino'''
} // namespace chatterino\n'''

View file

@ -32,7 +32,6 @@
#include "singletons/Updates.hpp"
#include "singletons/WindowManager.hpp"
#include "util/Helpers.hpp"
#include "util/IsBigEndian.hpp"
#include "util/PostToThread.hpp"
#include "util/RapidjsonHelpers.hpp"
#include "widgets/Notebook.hpp"

View file

@ -58,19 +58,6 @@ namespace {
auto size = *reinterpret_cast<uint32_t *>(size_c);
#if 0
bool bigEndian = isBigEndian();
// To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead
uint32_t size = 0;
if (bigEndian) {
size = size_c[3] | static_cast<uint32_t>(size_c[2]) << 8 |
static_cast<uint32_t>(size_c[1]) << 16 | static_cast<uint32_t>(size_c[0]) << 24;
} else {
size = size_c[0] | static_cast<uint32_t>(size_c[1]) << 8 |
static_cast<uint32_t>(size_c[2]) << 16 | static_cast<uint32_t>(size_c[3]) << 24;
}
#endif
std::unique_ptr<char[]> buffer(new char[size + 1]);
std::cin.read(buffer.get(), size);
*(buffer.get() + size) = '\0';

View file

@ -322,6 +322,8 @@ set(SOURCE_FILES
util/RapidjsonHelpers.hpp
util/RatelimitBucket.cpp
util/RatelimitBucket.hpp
util/SampleData.cpp
util/SampleData.hpp
util/SplitCommand.cpp
util/SplitCommand.hpp
util/StreamLink.cpp

View file

@ -17,6 +17,7 @@
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp"
#include "util/Clipboard.hpp"
#include "util/CombinePath.hpp"
#include "util/FormatTime.hpp"
#include "util/Helpers.hpp"
@ -176,6 +177,11 @@ bool appendWhisperMessageStringLocally(const QString &textNoEmoji)
return false;
}
const std::function<QString(const QString &, const ChannelPtr &)>
noOpPlaceholder = [](const auto &altText, const auto &channel) {
return altText;
};
const std::map<QString,
std::function<QString(const QString &, const ChannelPtr &)>>
COMMAND_VARS{
@ -239,6 +245,11 @@ const std::map<QString,
return name.isEmpty() ? altText : name;
},
},
// variables used in mod buttons and the like, these make no sense in normal commands, so they are left empty
{"input.text", noOpPlaceholder},
{"msg.id", noOpPlaceholder},
{"user.name", noOpPlaceholder},
{"msg.text", noOpPlaceholder},
};
} // namespace
@ -825,6 +836,7 @@ void CommandController::initialize(Settings &, Paths &paths)
}
return "";
});
this->registerCommand("/setgame", [](const QStringList &words,
const ChannelPtr channel) {
if (words.size() < 2)
@ -923,6 +935,7 @@ void CommandController::initialize(Settings &, Paths &paths)
return "";
});
this->registerCommand(
"/delete", [](const QStringList &words, ChannelPtr channel) -> QString {
// This is a wrapper over the standard Twitch /delete command
@ -965,6 +978,7 @@ void CommandController::initialize(Settings &, Paths &paths)
getApp()->twitch->sendRawMessage(words.mid(1).join(" "));
return "";
});
#ifndef NDEBUG
this->registerCommand(
"/fakemsg",
@ -981,6 +995,19 @@ void CommandController::initialize(Settings &, Paths &paths)
return "";
});
#endif
this->registerCommand(
"/copy", [](const QStringList &words, ChannelPtr channel) -> QString {
if (words.size() < 2)
{
channel->addMessage(
makeSystemMessage("Usage: /copy <text> - copies provided "
"text to clipboard."));
return "";
}
crossPlatformCopy(words.mid(1).join(" "));
return "";
});
}
void CommandController::save()
@ -1134,18 +1161,18 @@ QString CommandController::execCustomCommand(const QStringList &words,
auto varName = match.captured(4);
auto altText = match.captured(5); // alt text or empty string
auto var = COMMAND_VARS.find(varName);
auto var = context.find(varName);
if (var != COMMAND_VARS.end())
if (var != context.end())
{
result += var->second(altText, channel);
result += var->second.isEmpty() ? altText : var->second;
}
else
{
auto it = context.find(varName);
if (it != context.end())
auto it = COMMAND_VARS.find(varName);
if (it != COMMAND_VARS.end())
{
result += it->second.isEmpty() ? altText : it->second;
result += it->second(altText, channel);
}
else
{

View file

@ -1,5 +1,6 @@
#include "HighlightBadge.hpp"
#include "messages/SharedMessageBuilder.hpp"
#include "singletons/Resources.hpp"
namespace chatterino {
@ -86,21 +87,12 @@ bool HighlightBadge::compare(const QString &id, const Badge &badge) const
{
if (this->hasVersions_)
{
auto parts = id.split("/");
if (parts.size() == 2)
{
return parts.at(0).compare(badge.key_, Qt::CaseInsensitive) == 0 &&
parts.at(1).compare(badge.value_, Qt::CaseInsensitive) == 0;
}
else
{
return parts.at(0).compare(badge.key_, Qt::CaseInsensitive) == 0;
}
}
else
{
return id.compare(badge.key_, Qt::CaseInsensitive) == 0;
auto parts = SharedMessageBuilder::slashKeyValue(id);
return parts.first.compare(badge.key_, Qt::CaseInsensitive) == 0 &&
parts.second.compare(badge.value_, Qt::CaseInsensitive) == 0;
}
return id.compare(badge.key_, Qt::CaseInsensitive) == 0;
}
bool HighlightBadge::hasCustomSound() const

View file

@ -103,7 +103,8 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
0,
1,
}},
{"showSearch", ActionDefinition{"Search"}},
{"showSearch", ActionDefinition{"Search current channel"}},
{"showGlobalSearch", ActionDefinition{"Search all channels"}},
{"startWatching", ActionDefinition{"Start watching"}},
{"debug", ActionDefinition{"Show debug popup"}},
}},

View file

@ -339,6 +339,9 @@ void HotkeyController::addDefaults(std::set<QString> &addedHotkeys)
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
QKeySequence("Ctrl+F"), "showSearch",
std::vector<QString>(), "show search");
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
QKeySequence("Ctrl+Shift+F"), "showGlobalSearch",
std::vector<QString>(), "show global search");
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
QKeySequence("Ctrl+F5"), "reconnect",
std::vector<QString>(), "reconnect");

View file

@ -191,6 +191,9 @@ void NotificationController::fetchFakeChannels()
// we done fucked up.
qCWarning(chatterinoNotification)
<< "Failed to fetch live status for " << batch;
},
[]() {
// finally
});
}
}

View file

@ -138,14 +138,6 @@ namespace detail {
{
QVector<Frame<QImage>> frames;
if (reader.imageCount() == 0)
{
qCDebug(chatterinoImage)
<< "Error while reading image" << url.string << ": '"
<< reader.errorString() << "'";
return frames;
}
QImage image;
for (int index = 0; index < reader.imageCount(); ++index)
{
@ -413,8 +405,30 @@ void Image::actuallyLoad()
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer);
if (!reader.canRead())
{
qCDebug(chatterinoImage)
<< "Error: image cant be read " << shared->url().string;
return Failure;
}
const auto size = reader.size();
if (size.isEmpty())
{
return Failure;
}
// returns 1 for non-animated formats
if (reader.imageCount() <= 0)
{
qCDebug(chatterinoImage)
<< "Error: image has less than 1 frame "
<< shared->url().string << ": " << reader.errorString();
return Failure;
}
// use "double" to prevent int overflows
if (double(reader.size().width()) * double(reader.size().height()) *
if (double(size.width()) * double(size.height()) *
double(reader.imageCount()) * 4.0 >
double(Image::maxBytesRam))
{

View file

@ -2,6 +2,7 @@
#include "common/FlagsEnum.hpp"
#include "providers/twitch/TwitchBadge.hpp"
#include "util/QStringHash.hpp"
#include "widgets/helper/ScrollbarHighlight.hpp"
#include <QTime>
@ -65,7 +66,7 @@ struct Message : boost::noncopyable {
QColor usernameColor;
QDateTime serverReceivedTime;
std::vector<Badge> badges;
std::map<QString, QString> badgeInfos;
std::unordered_map<QString, QString> badgeInfos;
std::shared_ptr<QColor> highlightColor;
uint32_t count = 1;
std::vector<std::unique_ptr<MessageElement>> elements;

View file

@ -14,7 +14,6 @@
#include "util/FormatTime.hpp"
#include <QDateTime>
#include <QImageReader>
namespace chatterino {

View file

@ -4,7 +4,6 @@
#include "common/QLogging.hpp"
#include "controllers/ignores/IgnoreController.hpp"
#include "controllers/ignores/IgnorePhrase.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "singletons/Settings.hpp"
#include "singletons/WindowManager.hpp"
@ -36,33 +35,6 @@ namespace {
}
}
QStringList parseTagList(const QVariantMap &tags, const QString &key)
{
auto iterator = tags.find(key);
if (iterator == tags.end())
return QStringList{};
return iterator.value().toString().split(',', Qt::SkipEmptyParts);
}
std::vector<Badge> parseBadges(const QVariantMap &tags)
{
std::vector<Badge> badges;
for (QString badge : parseTagList(tags, "badges"))
{
QStringList parts = badge.split('/');
if (parts.size() != 2)
{
continue;
}
badges.emplace_back(parts[0], parts[1]);
}
return badges;
}
} // namespace
SharedMessageBuilder::SharedMessageBuilder(
@ -103,6 +75,46 @@ void SharedMessageBuilder::parse()
this->message().flags.set(MessageFlag::Collapsed);
}
// "foo/bar/baz,tri/hard" can be a valid badge-info tag
// In that case, valid map content should be 'split by slash' only once:
// {"foo": "bar/baz", "tri": "hard"}
std::pair<QString, QString> SharedMessageBuilder::slashKeyValue(
const QString &kvStr)
{
return {
// part before first slash (index 0 of section)
kvStr.section('/', 0, 0),
// part after first slash (index 1 of section)
kvStr.section('/', 1, -1),
};
}
std::vector<Badge> SharedMessageBuilder::parseBadgeTag(const QVariantMap &tags)
{
std::vector<Badge> b;
auto badgesIt = tags.constFind("badges");
if (badgesIt == tags.end())
{
return b;
}
auto badges = badgesIt.value().toString().split(',', Qt::SkipEmptyParts);
for (const QString &badge : badges)
{
if (!badge.contains('/'))
{
continue;
}
auto pair = SharedMessageBuilder::slashKeyValue(badge);
b.emplace_back(Badge{pair.first, pair.second});
}
return b;
}
bool SharedMessageBuilder::isIgnored() const
{
return isIgnoredMessage({
@ -332,7 +344,7 @@ void SharedMessageBuilder::parseHighlights()
}
// Highlight because of badge
auto badges = parseBadges(this->tags);
auto badges = this->parseBadgeTag(this->tags);
auto badgeHighlights = getCSettings().highlightedBadges.readOnly();
bool badgeHighlightSet = false;
for (const HighlightBadge &highlight : *badgeHighlights)

View file

@ -3,6 +3,7 @@
#include "common/Aliases.hpp"
#include "common/Outcome.hpp"
#include "messages/MessageColor.hpp"
#include "providers/twitch/TwitchBadge.hpp"
#include <IrcMessage>
#include <QColor>
@ -32,6 +33,11 @@ public:
virtual void triggerHighlights();
virtual MessagePtr build() = 0;
static std::pair<QString, QString> slashKeyValue(const QString &kvStr);
// Parses "badges" tag which contains a comma separated list of key-value elements
static std::vector<Badge> parseBadgeTag(const QVariantMap &tags);
protected:
virtual void parse();

View file

@ -33,4 +33,9 @@ Badge::Badge(QString key, QString value)
}
}
bool Badge::operator==(const Badge &other) const
{
return this->key_ == other.key_ && this->value_ == other.value_;
}
} // namespace chatterino

View file

@ -11,9 +11,13 @@ class Badge
public:
Badge(QString key, QString value);
QString key_; // e.g. bits
QString value_; // e.g. 100
QString extraValue_{}; // e.g. 5 (the number of months subscribed)
bool operator==(const Badge &other) const;
// Class members are fetched from both "badges" and "badge-info" tags
// E.g.: "badges": "subscriber/18", "badge-info": "subscriber/22"
QString key_; // subscriber
QString value_; // 18
//QString info_; // 22 (should be parsed separetly into an std::unordered_map)
MessageElementFlag flag_{
MessageElementFlag::BadgeVanity}; // badge slot it takes up
};

View file

@ -204,8 +204,18 @@ void TwitchBadges::loadEmoteImage(const QString &name, ImagePtr image,
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer);
QImage image;
if (reader.imageCount() == 0 || !reader.read(&image))
if (!reader.canRead() || reader.size().isEmpty())
{
return Failure;
}
QImage image = reader.read();
if (image.isNull())
{
return Failure;
}
if (reader.imageCount() <= 0)
{
return Failure;
}

View file

@ -708,6 +708,9 @@ void TwitchChannel::refreshLiveStatus()
},
[] {
// failure
},
[] {
// finally
});
}

View file

@ -26,6 +26,35 @@ using namespace std::chrono_literals;
namespace chatterino {
namespace {
// TODO: combine this with getEmoteSetBatches in TwitchAccount.cpp, maybe some templated thing
template <class T>
std::vector<T> getChannelsInBatches(T channels)
{
constexpr int batchSize = 100;
int batchCount = (channels.size() / batchSize) + 1;
std::vector<T> batches;
batches.reserve(batchCount);
for (int i = 0; i < batchCount; i++)
{
T batch;
// I hate you, msvc
int last = (std::min)(batchSize, channels.size() - batchSize * i);
for (int j = 0; j < last; j++)
{
batch.push_back(channels.at(j + (batchSize * i)));
}
batches.emplace_back(batch);
}
return batches;
}
} // namespace
TwitchIrcServer::TwitchIrcServer()
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
@ -302,60 +331,55 @@ std::shared_ptr<Channel> TwitchIrcServer::getChannelOrEmptyByID(
return Channel::getEmpty();
}
namespace {
// TODO: combine this with getEmoteSetBatches in TwitchAccount.cpp, maybe some templated thing
std::vector<QStringList> getChannelsInBatches(QStringList channels)
{
constexpr int batchSize = 100;
int batchCount = (channels.size() / batchSize) + 1;
std::vector<QStringList> batches;
batches.reserve(batchCount);
for (int i = 0; i < batchCount; i++)
{
QStringList batch;
// I hate you, msvc
int last = (std::min)(batchSize, channels.size() - batchSize * i);
for (int j = 0; j < last; j++)
{
batch.push_back(channels.at(j + (batchSize * i)));
}
batches.emplace_back(batch);
}
return batches;
}
} // namespace
void TwitchIrcServer::bulkRefreshLiveStatus()
{
QStringList userIDs;
this->forEachChannel([&userIDs](ChannelPtr chan) {
auto twitchChan = dynamic_cast<TwitchChannel *>(chan.get());
if (!twitchChan->roomId().isEmpty())
userIDs.push_back(twitchChan->roomId());
auto twitchChans = std::make_shared<QHash<QString, TwitchChannel *>>();
this->forEachChannel([twitchChans](ChannelPtr chan) {
auto tc = dynamic_cast<TwitchChannel *>(chan.get());
if (tc && !tc->roomId().isEmpty())
{
twitchChans->insert(tc->roomId(), tc);
}
});
for (const auto &batch : getChannelsInBatches(userIDs))
// iterate over batches of channel IDs
for (const auto &batch : getChannelsInBatches(twitchChans->keys()))
{
getHelix()->fetchStreams(
batch, QStringList(),
[this](std::vector<HelixStream> streams) {
batch, {},
[twitchChans](std::vector<HelixStream> streams) {
for (const auto &stream : streams)
{
auto chan = this->getChannelOrEmpty(stream.userLogin);
if (chan->getType() != Channel::Type::Twitch)
// remaining channels will be used later to set their stream status as offline
// so we use take(id) to remove it
auto tc = twitchChans->take(stream.userId);
if (tc == nullptr)
{
continue;
}
auto twitchChan = dynamic_cast<TwitchChannel *>(chan.get());
twitchChan->parseLiveStatus(true, stream);
tc->parseLiveStatus(true, stream);
}
},
[]() {
// failure
},
[batch, twitchChans] {
// All the channels that were not present in fetchStreams response should be assumed to be offline
// It is necessary to update their stream status in case they've gone live -> offline
// Otherwise some of them will be marked as live forever
for (const auto &chID : batch)
{
auto tc = twitchChans->value(chID);
// early out in case channel does not exist anymore
if (tc == nullptr)
{
continue;
}
tc->parseLiveStatus(false, {});
}
});
}
}

View file

@ -7,6 +7,7 @@
#include "messages/Message.hpp"
#include "providers/chatterino/ChatterinoBadges.hpp"
#include "providers/ffz/FfzBadges.hpp"
#include "providers/twitch/TwitchBadge.hpp"
#include "providers/twitch/TwitchBadges.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
@ -47,55 +48,6 @@ const QSet<QString> zeroWidthEmotes{
namespace chatterino {
namespace {
QStringList parseTagList(const QVariantMap &tags, const QString &key)
{
auto iterator = tags.find(key);
if (iterator == tags.end())
return QStringList{};
return iterator.value().toString().split(',', Qt::SkipEmptyParts);
}
std::map<QString, QString> parseBadgeInfos(const QVariantMap &tags)
{
std::map<QString, QString> badgeInfos;
for (QString badgeInfo : parseTagList(tags, "badge-info"))
{
QStringList parts = badgeInfo.split('/');
if (parts.size() != 2)
{
continue;
}
badgeInfos.emplace(parts[0], parts[1]);
}
return badgeInfos;
}
std::vector<Badge> parseBadges(const QVariantMap &tags)
{
std::vector<Badge> badges;
for (QString badge : parseTagList(tags, "badges"))
{
QStringList parts = badge.split('/');
if (parts.size() != 2)
{
continue;
}
badges.emplace_back(parts[0], parts[1]);
}
return badges;
}
} // namespace
TwitchMessageBuilder::TwitchMessageBuilder(
Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage,
const MessageParseArgs &_args)
@ -1033,6 +985,25 @@ boost::optional<EmotePtr> TwitchMessageBuilder::getTwitchBadge(
return boost::none;
}
std::unordered_map<QString, QString> TwitchMessageBuilder::parseBadgeInfoTag(
const QVariantMap &tags)
{
std::unordered_map<QString, QString> infoMap;
auto infoIt = tags.constFind("badge-info");
if (infoIt == tags.end())
return infoMap;
auto info = infoIt.value().toString().split(',', Qt::SkipEmptyParts);
for (const QString &badge : info)
{
infoMap.emplace(SharedMessageBuilder::slashKeyValue(badge));
}
return infoMap;
}
void TwitchMessageBuilder::appendTwitchBadges()
{
if (this->twitchChannel == nullptr)
@ -1040,8 +1011,8 @@ void TwitchMessageBuilder::appendTwitchBadges()
return;
}
auto badgeInfos = parseBadgeInfos(this->tags);
auto badges = parseBadges(this->tags);
auto badgeInfos = TwitchMessageBuilder::parseBadgeInfoTag(this->tags);
auto badges = this->parseBadgeTag(this->tags);
for (const auto &badge : badges)
{
@ -1091,7 +1062,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
// (tier + amount of months with leading zero if less than 100)
// e.g. 3054 - tier 3 4,5-year sub. 2108 - tier 2 9-year sub
const auto &subTier =
badge.value_.length() > 3 ? badge.value_.front() : '1';
badge.value_.length() > 3 ? badge.value_.at(0) : '1';
const auto &subMonths = badgeInfoIt->second;
tooltip +=
QString(" (%1%2 months)")
@ -1107,9 +1078,9 @@ void TwitchMessageBuilder::appendTwitchBadges()
{
auto predictionText =
badgeInfoIt->second
.replace("\\s", " ") // standard IRC escapes
.replace("\\:", ";")
.replace("\\\\", "\\")
.replace(R"(\s)", " ") // standard IRC escapes
.replace(R"(\:)", ";")
.replace(R"(\\)", R"(\)")
.replace("", ","); // twitch's comma escape
// Careful, the first character is RIGHT LOW PARAPHRASE BRACKET or U+2E1D, which just looks like a comma

View file

@ -68,6 +68,10 @@ public:
Channel *channel,
MessageBuilder *builder);
// Shares some common logic from SharedMessageBuilder::parseBadgeTag
static std::unordered_map<QString, QString> parseBadgeInfoTag(
const QVariantMap &tags);
private:
void parseUsernameColor() override;
void parseUsername() override;

View file

@ -145,7 +145,7 @@ void Helix::getUserFollowers(
void Helix::fetchStreams(
QStringList userIds, QStringList userLogins,
ResultCallback<std::vector<HelixStream>> successCallback,
HelixFailureCallback failureCallback)
HelixFailureCallback failureCallback, std::function<void()> finallyCallback)
{
QUrlQuery urlQuery;
@ -186,19 +186,21 @@ void Helix::fetchStreams(
// TODO: make better xd
failureCallback();
})
.finally(finallyCallback)
.execute();
}
void Helix::getStreamById(QString userId,
ResultCallback<bool, HelixStream> successCallback,
HelixFailureCallback failureCallback)
HelixFailureCallback failureCallback,
std::function<void()> finallyCallback)
{
QStringList userIds{std::move(userId)};
QStringList userLogins;
this->fetchStreams(
userIds, userLogins,
[successCallback, failureCallback](const auto &streams) {
[successCallback](const auto &streams) {
if (streams.empty())
{
successCallback(false, HelixStream());
@ -206,12 +208,13 @@ void Helix::getStreamById(QString userId,
}
successCallback(true, streams[0]);
},
failureCallback);
failureCallback, finallyCallback);
}
void Helix::getStreamByName(QString userName,
ResultCallback<bool, HelixStream> successCallback,
HelixFailureCallback failureCallback)
HelixFailureCallback failureCallback,
std::function<void()> finallyCallback)
{
QStringList userIds;
QStringList userLogins{std::move(userName)};
@ -226,7 +229,7 @@ void Helix::getStreamByName(QString userName,
}
successCallback(true, streams[0]);
},
failureCallback);
failureCallback, finallyCallback);
}
///

View file

@ -352,15 +352,18 @@ public:
virtual void fetchStreams(
QStringList userIds, QStringList userLogins,
ResultCallback<std::vector<HelixStream>> successCallback,
HelixFailureCallback failureCallback) = 0;
HelixFailureCallback failureCallback,
std::function<void()> finallyCallback) = 0;
virtual void getStreamById(
QString userId, ResultCallback<bool, HelixStream> successCallback,
HelixFailureCallback failureCallback) = 0;
HelixFailureCallback failureCallback,
std::function<void()> finallyCallback) = 0;
virtual void getStreamByName(
QString userName, ResultCallback<bool, HelixStream> successCallback,
HelixFailureCallback failureCallback) = 0;
HelixFailureCallback failureCallback,
std::function<void()> finallyCallback) = 0;
// https://dev.twitch.tv/docs/api/reference#get-games
virtual void fetchGames(
@ -469,15 +472,18 @@ public:
// https://dev.twitch.tv/docs/api/reference#get-streams
void fetchStreams(QStringList userIds, QStringList userLogins,
ResultCallback<std::vector<HelixStream>> successCallback,
HelixFailureCallback failureCallback) final;
HelixFailureCallback failureCallback,
std::function<void()> finallyCallback) final;
void getStreamById(QString userId,
ResultCallback<bool, HelixStream> successCallback,
HelixFailureCallback failureCallback) final;
HelixFailureCallback failureCallback,
std::function<void()> finallyCallback) final;
void getStreamByName(QString userName,
ResultCallback<bool, HelixStream> successCallback,
HelixFailureCallback failureCallback) final;
HelixFailureCallback failureCallback,
std::function<void()> finallyCallback) final;
// https://dev.twitch.tv/docs/api/reference#get-games
void fetchGames(QStringList gameIds, QStringList gameNames,

View file

@ -208,7 +208,7 @@ public:
/// Streamer Mode
EnumSetting<StreamerModeSetting> enableStreamerMode = {
"/streamerMode/enabled", StreamerModeSetting::DetectObs};
"/streamerMode/enabled", StreamerModeSetting::DetectStreamingSoftware};
BoolSetting streamerModeHideUsercardAvatars = {
"/streamerMode/hideUsercardAvatars", true};
BoolSetting streamerModeHideLinkThumbnails = {

View file

@ -1,6 +1,6 @@
#pragma once
#include <common/UniqueAccess.hpp>
#include "common/UniqueAccess.hpp"
#include <mutex>
#include <typeinfo>

View file

@ -1,12 +1,19 @@
#include "FormatTime.hpp"
namespace chatterino {
namespace {
void appendDuration(int count, QChar &&order, QString &outString)
void appendDuration(int count, QChar &&suffix, QString &out)
{
outString.append(QString::number(count));
outString.append(order);
if (!out.isEmpty())
{
out.append(' ');
}
out.append(QString::number(count));
out.append(suffix);
}
} // namespace
QString formatTime(int totalSeconds)
@ -25,26 +32,14 @@ QString formatTime(int totalSeconds)
}
if (hours > 0)
{
if (!res.isEmpty())
{
res.append(" ");
}
appendDuration(hours, 'h', res);
}
if (minutes > 0)
{
if (!res.isEmpty())
{
res.append(" ");
}
appendDuration(minutes, 'm', res);
}
if (seconds > 0)
{
if (!res.isEmpty())
{
res.append(" ");
}
appendDuration(seconds, 's', res);
}
return res;

View file

@ -1,13 +0,0 @@
#pragma once
namespace chatterino {
bool isBigEndian()
{
int test = 1;
char *p = reinterpret_cast<char *>(&test);
return p[0] == 0;
}
} // namespace chatterino

View file

@ -1,64 +0,0 @@
#pragma once
namespace chatterino {
std::vector<QString> getSampleCheerMessage()
{
// clang-format off
std::vector<QString> cheerMessageVector;
cheerMessageVector.push_back(R"(@badge-info=subscriber/4;badges=moderator/1,subscriber/3,sub-gifter/5;bits=2;color=#FF0000;display-name=69_faith_420;emotes=;flags=;id=c5fd49c7-ecbc-46dd-a790-c9f10fdaaa67;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567282184553;turbo=0;user-id=125608098;user-type=mod :69_faith_420!69_faith_420@69_faith_420.tmi.twitch.tv PRIVMSG #pajlada :cheer2 Stop what? I'm not doing anything.)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/4;badges=moderator/1,subscriber/3,sub-gifter/5;bits=2;color=#FF0000;display-name=69_faith_420;emotes=;flags=;id=397f4d2e-cac8-4689-922a-32709b9e8b4f;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567282159076;turbo=0;user-id=125608098;user-type=mod :69_faith_420!69_faith_420@69_faith_420.tmi.twitch.tv PRIVMSG #pajlada :cheer2 Who keeps getting their bits out now?)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=2;color=#FF0000;display-name=FlameGodFlann;emotes=;flags=;id=664ddc92-649d-4889-9641-208a6e62ef1e;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567282066199;turbo=0;user-id=56442185;user-type= :flamegodflann!flamegodflann@flamegodflann.tmi.twitch.tv PRIVMSG #pajlada :Cheer2 I'm saving my only can of Stella for your upcoming win, lets go!)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/3;badges=moderator/1,subscriber/3,bits/100;bits=10;color=#008000;display-name=k4izn;emotes=;flags=;id=3919af0b-93e0-412c-b238-d152f92ffea7;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567811485257;turbo=0;user-id=207114672;user-type=mod :k4izn!k4izn@k4izn.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Kleiner Cheer(s) !)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/12;badges=subscriber/12,bits/1000;bits=20;color=#00CCFF;display-name=YaBoiBurnsy;emotes=;flags=;id=5b53975d-b339-484f-a2a0-3ffbedde0df2;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567529634584;turbo=0;user-id=45258137;user-type= :yaboiburnsy!yaboiburnsy@yaboiburnsy.tmi.twitch.tv PRIVMSG #pajlada :ShowLove20)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=moderator/1,subscriber/0,bits-leader/2;bits=1;color=;display-name=jdfellie;emotes=;flags=18-22:A.3/P.5;id=28c8f4b7-b1e3-4404-b0f8-5cfe46411ef9;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567668177856;turbo=0;user-id=137619637;user-type=mod :jdfellie!jdfellie@jdfellie.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 take a bit bitch)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/2;bits=30;color=#EC3B83;display-name=Sammay;emotes=;flags=;id=ccf058a6-c1f1-45de-a764-fc8f96f21449;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566719874294;turbo=0;user-id=58283830;user-type= :sammay!sammay@sammay.tmi.twitch.tv PRIVMSG #pajlada :ShowLove30 @Emperor_Zhang)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/2;bits=6;color=#97E7FF;display-name=Emperor_Zhang;emotes=;flags=;id=53bab01b-9f6c-4123-a852-9916ab371cf9;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566719803345;turbo=0;user-id=105292882;user-type= :emperor_zhang!emperor_zhang@emperor_zhang.tmi.twitch.tv PRIVMSG #pajlada :uni6)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=5;color=#97E7FF;display-name=Emperor_Zhang;emotes=;flags=;id=545caec6-8b5f-460a-8b4b-3e407e179689;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566704926380;turbo=0;user-id=105292882;user-type= :emperor_zhang!emperor_zhang@emperor_zhang.tmi.twitch.tv PRIVMSG #pajlada :VoHiYo5)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=50;color=;display-name=Schmiddi55;emotes=;flags=;id=777f1018-941d-48aa-bf4e-ed8053d556c8;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567708393343;turbo=0;user-id=101444120;user-type= :schmiddi55!schmiddi55@schmiddi55.tmi.twitch.tv PRIVMSG #pajlada :cheer50 sere ihr radlertrinker)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/3;badges=subscriber/3,sub-gifter/10;bits=100;color=#0000FF;display-name=MLPTheChad;emotes=;flags=87-91:P.5;id=ed7db31e-884b-4761-9c88-b1676caa8814;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567681752733;turbo=0;user-id=63179867;user-type= :mlpthechad!mlpthechad@mlpthechad.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10 Statistically speaking, 10 out of 10 constipated people don't give a shit.)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/3;badges=subscriber/3,sub-gifter/10;bits=100;color=#0000FF;display-name=MLPTheChad;emotes=;flags=;id=506b482a-515a-4914-a694-2c69d2add23a;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567681618814;turbo=0;user-id=63179867;user-type= :mlpthechad!mlpthechad@mlpthechad.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10 That's some SUB par gameplay, Dabier.)");
cheerMessageVector.push_back(R"(@badge-info=;badges=premium/1;bits=100;color=;display-name=AkiraKurusu__;emotes=;flags=;id=6e343f5d-0e0e-47f7-bf6d-d5d7bf18b95a;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567765732657;turbo=0;user-id=151679027;user-type= :akirakurusu__!akirakurusu__@akirakurusu__.tmi.twitch.tv PRIVMSG #pajlada :TriHard100)");
cheerMessageVector.push_back(R"(@badge-info=;badges=premium/1;bits=1;color=;display-name=AkiraKurusu__;emotes=;flags=;id=dfdf6c2f-abee-4a4b-99fe-0d0b221f07de;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567765295301;turbo=0;user-id=151679027;user-type= :akirakurusu__!akirakurusu__@akirakurusu__.tmi.twitch.tv PRIVMSG #pajlada :TriHard1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=500;color=#0000FF;display-name=Stabbr;emotes=;flags=;id=e28b384e-fb6a-4da5-9a36-1b6153c6089d;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567648284623;turbo=0;user-id=183081176;user-type= :stabbr!stabbr@stabbr.tmi.twitch.tv PRIVMSG #pajlada :cheer500 Gotta be on top)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=subscriber/0,bits-leader/1;bits=100;color=;display-name=dbf_sub;emotes=;flags=;id=7cf317b8-6e28-4615-a0ba-e0bbaa0d4b29;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567646349560;turbo=0;user-id=450101746;user-type= :dbf_sub!dbf_sub@dbf_sub.tmi.twitch.tv PRIVMSG #pajlada :EleGiggle100)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=1;color=;display-name=dbf_sub;emotes=;flags=;id=43b5fc97-e7cc-4ac1-8d7e-7504c435c3f1;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567643510222;turbo=0;user-id=450101746;user-type= :dbf_sub!dbf_sub@dbf_sub.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/2;bits=100;color=;display-name=RobertsonRobotics;emotes=;flags=;id=598dfa14-23e9-4e45-a2fe-7a0263828817;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567873463820;turbo=0;user-id=117177721;user-type= :robertsonrobotics!robertsonrobotics@robertsonrobotics.tmi.twitch.tv PRIVMSG #pajlada :firstCheer100 This is so cool! Cant wait for the competition!)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=18;color=#1E90FF;display-name=Vipacman11;emotes=;flags=;id=07f59664-0c75-459e-b137-26c8d03e44be;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567873210379;turbo=0;user-id=89634839;user-type= :vipacman11!vipacman11@vipacman11.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=sub-gifter/5;bits=100;color=#FF7F50;display-name=darkside_sinner;emotes=;flags=;id=090102b3-369d-4ce4-ad1f-283849b10de0;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567822075293;turbo=0;user-id=104942909;user-type= :darkside_sinner!darkside_sinner@darkside_sinner.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10)");
cheerMessageVector.push_back(R"(@badge-info=;badges=sub-gifter/5;bits=200;color=#FF7F50;display-name=darkside_sinner;emotes=;flags=;id=2bdf7846-5ffa-4798-a397-997e7209a6d0;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567821695287;turbo=0;user-id=104942909;user-type= :darkside_sinner!darkside_sinner@darkside_sinner.tmi.twitch.tv PRIVMSG #pajlada :Subway200 bonus20)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=50;color=#0000FF;display-name=SincereBC;emotes=;flags=;id=b8c9236b-aeb9-4c72-a191-593e33c6c3f1;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567818308913;turbo=0;user-id=146097597;user-type= :sincerebc!sincerebc@sincerebc.tmi.twitch.tv PRIVMSG #pajlada :cheer50)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=1;color=#FF0000;display-name=AngryCh33s3puff;emotes=;flags=;id=6ab62185-ac1b-4ee5-bd93-165009917078;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567474810480;turbo=0;user-id=55399500;user-type= :angrych33s3puff!angrych33s3puff@angrych33s3puff.tmi.twitch.tv PRIVMSG #pajlada :cheer1 for the chair!)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/3;badges=moderator/1,subscriber/0,bits/1000;bits=1500;color=#5F9EA0;display-name=LaurenJW28;emotes=;flags=;id=2403678c-6109-43ac-b3b5-1f5230f91729;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567746107991;turbo=0;user-id=244354979;user-type=mod :laurenjw28!laurenjw28@laurenjw28.tmi.twitch.tv PRIVMSG #pajlada :Cheer1000 Cheer100 Cheer100 Cheer100 Cheer100 Cheer100)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=5;color=#5F9EA0;display-name=drkwings;emotes=;flags=;id=ad45dae5-b985-4526-9b9e-0bdba2d23289;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567742106689;turbo=0;user-id=440230526;user-type= :drkwings!drkwings@drkwings.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1 SeemsGood1 SeemsGood1 SeemsGood1 SeemsGood1)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/16;badges=subscriber/12,bits/1000;bits=1;color=;display-name=mustangbugatti;emotes=;flags=;id=ee987ee9-46a4-4c06-bf66-2cafff5d4cdd;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567883658780;turbo=0;user-id=115948494;user-type= :mustangbugatti!mustangbugatti@mustangbugatti.tmi.twitch.tv PRIVMSG #pajlada :(In clarkson accent) Some say...the only number in his contacts is himself..... And...that he is the international butt-dial champion... All we know is.... HES CALLED THE STIG Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/2;badges=subscriber/0,bits/1000;bits=1;color=;display-name=derpysaurus1;emotes=;flags=;id=c41c3d8b-c591-4db0-87e7-a78c5536de82;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567883655116;turbo=0;user-id=419221818;user-type= :derpysaurus1!derpysaurus1@derpysaurus1.tmi.twitch.tv PRIVMSG #pajlada :cheer1 OMG ur back yaaaaaaaaaaaaaaaaaaaaayyyyyyyyy)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/5;badges=subscriber/0,premium/1;bits=1;color=#8A2BE2;display-name=sirlordstallion;emotes=;flags=;id=61a87aeb-88b1-42f9-90f5-74429d8bf387;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567882978939;turbo=0;user-id=92145441;user-type= :sirlordstallion!sirlordstallion@sirlordstallion.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Alex is definetly not putting his eggs in Narreths basket)");
cheerMessageVector.push_back(R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=1;color=;display-name=xplosivegingerx;emotes=;flags=;id=f8aac1e0-050a-44bf-abcc-c0cf12cbedfc;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567882249072;turbo=0;user-id=151265906;user-type= :xplosivegingerx!xplosivegingerx@xplosivegingerx.tmi.twitch.tv PRIVMSG #pajlada :Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=500;color=;display-name=AlexJohanning;emotes=;flags=;id=4e4229a3-e7f2-4082-8c55-47d42db3b09c;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567881969862;turbo=0;user-id=190390930;user-type= :alexjohanning!alexjohanning@alexjohanning.tmi.twitch.tv PRIVMSG #pajlada :cheer500)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=245;color=;display-name=undonebunion6;emotes=;flags=;id=331ec583-0a80-4299-9206-0efd9e33d934;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567881553759;turbo=0;user-id=452974274;user-type= :undonebunion6!undonebunion6@undonebunion6.tmi.twitch.tv PRIVMSG #pajlada :cheer245 can I join?)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/100;bits=100;color=;display-name=therealruffnix;emotes=;flags=61-67:S.6;id=25f567ad-ac95-45ab-b12e-4d647f6a2345;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567524218162;turbo=0;user-id=55059620;user-type= :therealruffnix!therealruffnix@therealruffnix.tmi.twitch.tv PRIVMSG #pajlada :cheer100 This is the kind of ASMR I'm missing on YouTube and PornHub)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=1;color=;display-name=BeamMeUpSnotty;emotes=;flags=;id=8022f41f-dcb8-42f2-b46a-04d4a99180bd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567270037926;turbo=0;user-id=261679182;user-type= :beammeupsnotty!beammeupsnotty@beammeupsnotty.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=10;color=#00FF7F;display-name=EXDE_HUN;emotes=;flags=;id=60d8835b-23fa-418c-96ca-5874e5d5e8ba;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566654664248;turbo=0;user-id=129793695;user-type= :exde_hun!exde_hun@exde_hun.tmi.twitch.tv PRIVMSG #pajlada :PogChamp10)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=fd6c5507-3a4e-4d24-8f6e-fadf07f520d3;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824273752;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=7003f119-b9a6-4319-a1e8-8e99f96ab01a;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824186437;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/3;bits=10;color=;display-name=slyckity;emotes=;flags=;id=3f7de686-77f6-46d2-919e-404312c6676f;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824128736;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/3;bits=10;color=;display-name=slyckity;emotes=;flags=;id=9e830ed3-8735-4ccb-9a8b-80466598ca19;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824118921;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=377;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=262f4d54-9b21-4f13-aac3-6d3b1051282f;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440897074;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :NotLikeThis377)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=144;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3556e0ad-b5f8-4190-9c4c-e39c1940d191;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440861545;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :bday144)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=89;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=96e380a5-786d-44b8-819a-529b6adb06ac;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440848361;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :SwiftRage89)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=34;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=76239011-65fa-4f6a-a6d6-dc5d5dcbd674;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440816630;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :MrDestructoid34)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=21;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=4c05c97c-7b6c-4ae9-bc91-04e98240c1d5;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440806389;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :TriHard21)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=8;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3b2ecce7-842e-429e-b6c8-9456c4646362;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440774009;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :EleGiggle8)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=5;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3b8736d1-832d-4152-832a-50c526714fd1;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440762580;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :uni5)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=3;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=c13a1540-2a03-4c7d-af50-cb20ed88cefd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440750103;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :Party3)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/1;bits=2;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=5d889eeb-b6b9-4a4e-91ff-0aecdf297edd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440738337;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :ShowLove2)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=1;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=da47f91a-40d3-4209-ba1c-0219d8b8ecaf;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440720363;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :Scoops1)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits/1;bits=10;color=#8A2BE2;display-name=EkimSky;emotes=;flags=;id=8adea5b4-7430-44ea-a666-5ebaceb69441;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567833047623;turbo=0;user-id=42132818;user-type= :ekimsky!ekimsky@ekimsky.tmi.twitch.tv PRIVMSG #pajlada :Hi Cheer10)");
cheerMessageVector.push_back(R"(@badge-info=;badges=bits-leader/2;bits=500;color=;display-name=godkiller76;emotes=;flags=;id=80e86bcc-d048-44f3-8073-9a1014568e0c;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567753685704;turbo=0;user-id=258838478;user-type= :godkiller76!godkiller76@godkiller76.tmi.twitch.tv PRIVMSG #pajlada :Party100 Party100 Party100 Party100 Party100)");
return cheerMessageVector;
// clang-format on
};
} // namespace chatterino

328
src/util/SampleData.cpp Normal file
View file

@ -0,0 +1,328 @@
#include "SampleData.hpp"
namespace chatterino {
/// Sample messages coming from IRC
const QStringList &getSampleCheerMessages()
{
static QStringList list{
R"(@badge-info=subscriber/4;badges=moderator/1,subscriber/3,sub-gifter/5;bits=2;color=#FF0000;display-name=69_faith_420;emotes=;flags=;id=c5fd49c7-ecbc-46dd-a790-c9f10fdaaa67;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567282184553;turbo=0;user-id=125608098;user-type=mod :69_faith_420!69_faith_420@69_faith_420.tmi.twitch.tv PRIVMSG #pajlada :cheer2 Stop what? I'm not doing anything.)",
R"(@badge-info=subscriber/4;badges=moderator/1,subscriber/3,sub-gifter/5;bits=2;color=#FF0000;display-name=69_faith_420;emotes=;flags=;id=397f4d2e-cac8-4689-922a-32709b9e8b4f;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567282159076;turbo=0;user-id=125608098;user-type=mod :69_faith_420!69_faith_420@69_faith_420.tmi.twitch.tv PRIVMSG #pajlada :cheer2 Who keeps getting their bits out now?)",
R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=2;color=#FF0000;display-name=FlameGodFlann;emotes=;flags=;id=664ddc92-649d-4889-9641-208a6e62ef1e;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567282066199;turbo=0;user-id=56442185;user-type= :flamegodflann!flamegodflann@flamegodflann.tmi.twitch.tv PRIVMSG #pajlada :Cheer2 I'm saving my only can of Stella for your upcoming win, lets go!)",
R"(@badge-info=subscriber/3;badges=moderator/1,subscriber/3,bits/100;bits=10;color=#008000;display-name=k4izn;emotes=;flags=;id=3919af0b-93e0-412c-b238-d152f92ffea7;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567811485257;turbo=0;user-id=207114672;user-type=mod :k4izn!k4izn@k4izn.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Kleiner Cheer(s) !)",
R"(@badge-info=subscriber/12;badges=subscriber/12,bits/1000;bits=20;color=#00CCFF;display-name=YaBoiBurnsy;emotes=;flags=;id=5b53975d-b339-484f-a2a0-3ffbedde0df2;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567529634584;turbo=0;user-id=45258137;user-type= :yaboiburnsy!yaboiburnsy@yaboiburnsy.tmi.twitch.tv PRIVMSG #pajlada :ShowLove20)",
R"(@badge-info=subscriber/1;badges=moderator/1,subscriber/0,bits-leader/2;bits=1;color=;display-name=jdfellie;emotes=;flags=18-22:A.3/P.5;id=28c8f4b7-b1e3-4404-b0f8-5cfe46411ef9;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567668177856;turbo=0;user-id=137619637;user-type=mod :jdfellie!jdfellie@jdfellie.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 take a bit bitch)",
R"(@badge-info=;badges=bits-leader/2;bits=30;color=#EC3B83;display-name=Sammay;emotes=;flags=;id=ccf058a6-c1f1-45de-a764-fc8f96f21449;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566719874294;turbo=0;user-id=58283830;user-type= :sammay!sammay@sammay.tmi.twitch.tv PRIVMSG #pajlada :ShowLove30 @Emperor_Zhang)",
R"(@badge-info=;badges=bits-leader/2;bits=6;color=#97E7FF;display-name=Emperor_Zhang;emotes=;flags=;id=53bab01b-9f6c-4123-a852-9916ab371cf9;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566719803345;turbo=0;user-id=105292882;user-type= :emperor_zhang!emperor_zhang@emperor_zhang.tmi.twitch.tv PRIVMSG #pajlada :uni6)",
R"(@badge-info=;badges=bits/1;bits=5;color=#97E7FF;display-name=Emperor_Zhang;emotes=;flags=;id=545caec6-8b5f-460a-8b4b-3e407e179689;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566704926380;turbo=0;user-id=105292882;user-type= :emperor_zhang!emperor_zhang@emperor_zhang.tmi.twitch.tv PRIVMSG #pajlada :VoHiYo5)",
R"(@badge-info=;badges=bits/100;bits=50;color=;display-name=Schmiddi55;emotes=;flags=;id=777f1018-941d-48aa-bf4e-ed8053d556c8;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567708393343;turbo=0;user-id=101444120;user-type= :schmiddi55!schmiddi55@schmiddi55.tmi.twitch.tv PRIVMSG #pajlada :cheer50 sere ihr radlertrinker)",
R"(@badge-info=subscriber/3;badges=subscriber/3,sub-gifter/10;bits=100;color=#0000FF;display-name=MLPTheChad;emotes=;flags=87-91:P.5;id=ed7db31e-884b-4761-9c88-b1676caa8814;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567681752733;turbo=0;user-id=63179867;user-type= :mlpthechad!mlpthechad@mlpthechad.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10 Statistically speaking, 10 out of 10 constipated people don't give a shit.)",
R"(@badge-info=subscriber/3;badges=subscriber/3,sub-gifter/10;bits=100;color=#0000FF;display-name=MLPTheChad;emotes=;flags=;id=506b482a-515a-4914-a694-2c69d2add23a;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567681618814;turbo=0;user-id=63179867;user-type= :mlpthechad!mlpthechad@mlpthechad.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10 That's some SUB par gameplay, Dabier.)",
R"(@badge-info=;badges=premium/1;bits=100;color=;display-name=AkiraKurusu__;emotes=;flags=;id=6e343f5d-0e0e-47f7-bf6d-d5d7bf18b95a;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567765732657;turbo=0;user-id=151679027;user-type= :akirakurusu__!akirakurusu__@akirakurusu__.tmi.twitch.tv PRIVMSG #pajlada :TriHard100)",
R"(@badge-info=;badges=premium/1;bits=1;color=;display-name=AkiraKurusu__;emotes=;flags=;id=dfdf6c2f-abee-4a4b-99fe-0d0b221f07de;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567765295301;turbo=0;user-id=151679027;user-type= :akirakurusu__!akirakurusu__@akirakurusu__.tmi.twitch.tv PRIVMSG #pajlada :TriHard1)",
R"(@badge-info=;badges=bits/100;bits=500;color=#0000FF;display-name=Stabbr;emotes=;flags=;id=e28b384e-fb6a-4da5-9a36-1b6153c6089d;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567648284623;turbo=0;user-id=183081176;user-type= :stabbr!stabbr@stabbr.tmi.twitch.tv PRIVMSG #pajlada :cheer500 Gotta be on top)",
R"(@badge-info=subscriber/1;badges=subscriber/0,bits-leader/1;bits=100;color=;display-name=dbf_sub;emotes=;flags=;id=7cf317b8-6e28-4615-a0ba-e0bbaa0d4b29;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567646349560;turbo=0;user-id=450101746;user-type= :dbf_sub!dbf_sub@dbf_sub.tmi.twitch.tv PRIVMSG #pajlada :EleGiggle100)",
R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=1;color=;display-name=dbf_sub;emotes=;flags=;id=43b5fc97-e7cc-4ac1-8d7e-7504c435c3f1;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567643510222;turbo=0;user-id=450101746;user-type= :dbf_sub!dbf_sub@dbf_sub.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1)",
R"(@badge-info=;badges=bits-leader/2;bits=100;color=;display-name=RobertsonRobotics;emotes=;flags=;id=598dfa14-23e9-4e45-a2fe-7a0263828817;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567873463820;turbo=0;user-id=117177721;user-type= :robertsonrobotics!robertsonrobotics@robertsonrobotics.tmi.twitch.tv PRIVMSG #pajlada :firstCheer100 This is so cool! Cant wait for the competition!)",
R"(@badge-info=;badges=bits/100;bits=18;color=#1E90FF;display-name=Vipacman11;emotes=;flags=;id=07f59664-0c75-459e-b137-26c8d03e44be;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567873210379;turbo=0;user-id=89634839;user-type= :vipacman11!vipacman11@vipacman11.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)",
R"(@badge-info=;badges=sub-gifter/5;bits=100;color=#FF7F50;display-name=darkside_sinner;emotes=;flags=;id=090102b3-369d-4ce4-ad1f-283849b10de0;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567822075293;turbo=0;user-id=104942909;user-type= :darkside_sinner!darkside_sinner@darkside_sinner.tmi.twitch.tv PRIVMSG #pajlada :Subway100 bonus10)",
R"(@badge-info=;badges=sub-gifter/5;bits=200;color=#FF7F50;display-name=darkside_sinner;emotes=;flags=;id=2bdf7846-5ffa-4798-a397-997e7209a6d0;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567821695287;turbo=0;user-id=104942909;user-type= :darkside_sinner!darkside_sinner@darkside_sinner.tmi.twitch.tv PRIVMSG #pajlada :Subway200 bonus20)",
R"(@badge-info=;badges=bits/1;bits=50;color=#0000FF;display-name=SincereBC;emotes=;flags=;id=b8c9236b-aeb9-4c72-a191-593e33c6c3f1;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567818308913;turbo=0;user-id=146097597;user-type= :sincerebc!sincerebc@sincerebc.tmi.twitch.tv PRIVMSG #pajlada :cheer50)",
R"(@badge-info=;badges=bits/1;bits=1;color=#FF0000;display-name=AngryCh33s3puff;emotes=;flags=;id=6ab62185-ac1b-4ee5-bd93-165009917078;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567474810480;turbo=0;user-id=55399500;user-type= :angrych33s3puff!angrych33s3puff@angrych33s3puff.tmi.twitch.tv PRIVMSG #pajlada :cheer1 for the chair!)",
R"(@badge-info=subscriber/3;badges=moderator/1,subscriber/0,bits/1000;bits=1500;color=#5F9EA0;display-name=LaurenJW28;emotes=;flags=;id=2403678c-6109-43ac-b3b5-1f5230f91729;mod=1;room-id=111448817;subscriber=1;tmi-sent-ts=1567746107991;turbo=0;user-id=244354979;user-type=mod :laurenjw28!laurenjw28@laurenjw28.tmi.twitch.tv PRIVMSG #pajlada :Cheer1000 Cheer100 Cheer100 Cheer100 Cheer100 Cheer100)",
R"(@badge-info=;badges=bits/1;bits=5;color=#5F9EA0;display-name=drkwings;emotes=;flags=;id=ad45dae5-b985-4526-9b9e-0bdba2d23289;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567742106689;turbo=0;user-id=440230526;user-type= :drkwings!drkwings@drkwings.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1 SeemsGood1 SeemsGood1 SeemsGood1 SeemsGood1)",
R"(@badge-info=subscriber/16;badges=subscriber/12,bits/1000;bits=1;color=;display-name=mustangbugatti;emotes=;flags=;id=ee987ee9-46a4-4c06-bf66-2cafff5d4cdd;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567883658780;turbo=0;user-id=115948494;user-type= :mustangbugatti!mustangbugatti@mustangbugatti.tmi.twitch.tv PRIVMSG #pajlada :(In clarkson accent) Some say...the only number in his contacts is himself..... And...that he is the international butt-dial champion... All we know is.... HES CALLED THE STIG Cheer1)",
R"(@badge-info=subscriber/2;badges=subscriber/0,bits/1000;bits=1;color=;display-name=derpysaurus1;emotes=;flags=;id=c41c3d8b-c591-4db0-87e7-a78c5536de82;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567883655116;turbo=0;user-id=419221818;user-type= :derpysaurus1!derpysaurus1@derpysaurus1.tmi.twitch.tv PRIVMSG #pajlada :cheer1 OMG ur back yaaaaaaaaaaaaaaaaaaaaayyyyyyyyy)",
R"(@badge-info=subscriber/5;badges=subscriber/0,premium/1;bits=1;color=#8A2BE2;display-name=sirlordstallion;emotes=;flags=;id=61a87aeb-88b1-42f9-90f5-74429d8bf387;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567882978939;turbo=0;user-id=92145441;user-type= :sirlordstallion!sirlordstallion@sirlordstallion.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Alex is definetly not putting his eggs in Narreths basket)",
R"(@badge-info=subscriber/1;badges=subscriber/0,bits/1;bits=1;color=;display-name=xplosivegingerx;emotes=;flags=;id=f8aac1e0-050a-44bf-abcc-c0cf12cbedfc;mod=0;room-id=111448817;subscriber=1;tmi-sent-ts=1567882249072;turbo=0;user-id=151265906;user-type= :xplosivegingerx!xplosivegingerx@xplosivegingerx.tmi.twitch.tv PRIVMSG #pajlada :Cheer1)",
R"(@badge-info=;badges=bits/100;bits=500;color=;display-name=AlexJohanning;emotes=;flags=;id=4e4229a3-e7f2-4082-8c55-47d42db3b09c;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567881969862;turbo=0;user-id=190390930;user-type= :alexjohanning!alexjohanning@alexjohanning.tmi.twitch.tv PRIVMSG #pajlada :cheer500)",
R"(@badge-info=;badges=bits-leader/1;bits=245;color=;display-name=undonebunion6;emotes=;flags=;id=331ec583-0a80-4299-9206-0efd9e33d934;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567881553759;turbo=0;user-id=452974274;user-type= :undonebunion6!undonebunion6@undonebunion6.tmi.twitch.tv PRIVMSG #pajlada :cheer245 can I join?)",
R"(@badge-info=;badges=bits/100;bits=100;color=;display-name=therealruffnix;emotes=;flags=61-67:S.6;id=25f567ad-ac95-45ab-b12e-4d647f6a2345;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567524218162;turbo=0;user-id=55059620;user-type= :therealruffnix!therealruffnix@therealruffnix.tmi.twitch.tv PRIVMSG #pajlada :cheer100 This is the kind of ASMR I'm missing on YouTube and PornHub)",
R"(@badge-info=;badges=bits/1;bits=1;color=;display-name=BeamMeUpSnotty;emotes=;flags=;id=8022f41f-dcb8-42f2-b46a-04d4a99180bd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567270037926;turbo=0;user-id=261679182;user-type= :beammeupsnotty!beammeupsnotty@beammeupsnotty.tmi.twitch.tv PRIVMSG #pajlada :SeemsGood1)",
R"(@badge-info=;badges=bits/1;bits=10;color=#00FF7F;display-name=EXDE_HUN;emotes=;flags=;id=60d8835b-23fa-418c-96ca-5874e5d5e8ba;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1566654664248;turbo=0;user-id=129793695;user-type= :exde_hun!exde_hun@exde_hun.tmi.twitch.tv PRIVMSG #pajlada :PogChamp10)",
R"(@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=fd6c5507-3a4e-4d24-8f6e-fadf07f520d3;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824273752;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)",
R"(@badge-info=;badges=bits-leader/3;bits=5;color=;display-name=slyckity;emotes=;flags=;id=7003f119-b9a6-4319-a1e8-8e99f96ab01a;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824186437;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)",
R"(@badge-info=;badges=bits-leader/3;bits=10;color=;display-name=slyckity;emotes=;flags=;id=3f7de686-77f6-46d2-919e-404312c6676f;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824128736;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)",
R"(@badge-info=;badges=bits-leader/3;bits=10;color=;display-name=slyckity;emotes=;flags=;id=9e830ed3-8735-4ccb-9a8b-80466598ca19;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567824118921;turbo=0;user-id=143114011;user-type= :slyckity!slyckity@slyckity.tmi.twitch.tv PRIVMSG #pajlada :Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1 Cheer1)",
R"(@badge-info=;badges=bits-leader/1;bits=377;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=262f4d54-9b21-4f13-aac3-6d3b1051282f;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440897074;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :NotLikeThis377)",
R"(@badge-info=;badges=bits-leader/1;bits=144;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3556e0ad-b5f8-4190-9c4c-e39c1940d191;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440861545;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :bday144)",
R"(@badge-info=;badges=bits-leader/1;bits=89;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=96e380a5-786d-44b8-819a-529b6adb06ac;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440848361;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :SwiftRage89)",
R"(@badge-info=;badges=bits-leader/1;bits=34;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=76239011-65fa-4f6a-a6d6-dc5d5dcbd674;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440816630;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :MrDestructoid34)",
R"(@badge-info=;badges=bits-leader/1;bits=21;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=4c05c97c-7b6c-4ae9-bc91-04e98240c1d5;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440806389;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :TriHard21)",
R"(@badge-info=;badges=bits-leader/1;bits=8;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3b2ecce7-842e-429e-b6c8-9456c4646362;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440774009;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :EleGiggle8)",
R"(@badge-info=;badges=bits-leader/1;bits=5;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=3b8736d1-832d-4152-832a-50c526714fd1;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440762580;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :uni5)",
R"(@badge-info=;badges=bits-leader/1;bits=3;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=c13a1540-2a03-4c7d-af50-cb20ed88cefd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440750103;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :Party3)",
R"(@badge-info=;badges=bits-leader/1;bits=2;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=5d889eeb-b6b9-4a4e-91ff-0aecdf297edd;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440738337;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :ShowLove2)",
R"(@badge-info=;badges=bits/1;bits=1;color=#00FF7F;display-name=Baekjoon;emotes=;flags=;id=da47f91a-40d3-4209-ba1c-0219d8b8ecaf;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567440720363;turbo=0;user-id=73587716;user-type= :baekjoon!baekjoon@baekjoon.tmi.twitch.tv PRIVMSG #pajlada :Scoops1)",
R"(@badge-info=;badges=bits/1;bits=10;color=#8A2BE2;display-name=EkimSky;emotes=;flags=;id=8adea5b4-7430-44ea-a666-5ebaceb69441;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567833047623;turbo=0;user-id=42132818;user-type= :ekimsky!ekimsky@ekimsky.tmi.twitch.tv PRIVMSG #pajlada :Hi Cheer10)",
R"(@badge-info=;badges=bits-leader/2;bits=500;color=;display-name=godkiller76;emotes=;flags=;id=80e86bcc-d048-44f3-8073-9a1014568e0c;mod=0;room-id=111448817;subscriber=0;tmi-sent-ts=1567753685704;turbo=0;user-id=258838478;user-type= :godkiller76!godkiller76@godkiller76.tmi.twitch.tv PRIVMSG #pajlada :Party100 Party100 Party100 Party100 Party100)",
};
return list;
}
const QStringList &getSampleSubMessages()
{
static QStringList list{
R"(@badges=staff/1,broadcaster/1,turbo/1;color=#008000;display-name=ronni;emotes=;id=db25007f-7a18-43eb-9379-80131e44d633;login=ronni;mod=0;msg-id=resub;msg-param-months=6;msg-param-sub-plan=Prime;msg-param-sub-plan-name=Prime;room-id=1337;subscriber=1;system-msg=ronni\shas\ssubscribed\sfor\s6\smonths!;tmi-sent-ts=1507246572675;turbo=1;user-id=1337;user-type=staff :tmi.twitch.tv USERNOTICE #pajlada :Great stream -- keep it up!)",
R"(@badges=staff/1,premium/1;color=#0000FF;display-name=TWW2;emotes=;id=e9176cd8-5e22-4684-ad40-ce53c2561c5e;login=tww2;mod=0;msg-id=subgift;msg-param-months=1;msg-param-recipient-display-name=Mr_Woodchuck;msg-param-recipient-id=89614178;msg-param-recipient-name=mr_woodchuck;msg-param-sub-plan-name=House\sof\sNyoro~n;msg-param-sub-plan=1000;room-id=19571752;subscriber=0;system-msg=TWW2\sgifted\sa\sTier\s1\ssub\sto\sMr_Woodchuck!;tmi-sent-ts=1521159445153;turbo=0;user-id=13405587;user-type=staff :tmi.twitch.tv USERNOTICE #pajlada)",
// hyperbolicxd gifted a sub to quote_if_nam
R"(@badges=subscriber/0,premium/1;color=#00FF7F;display-name=hyperbolicxd;emotes=;id=b20ef4fe-cba8-41d0-a371-6327651dc9cc;login=hyperbolicxd;mod=0;msg-id=subgift;msg-param-months=1;msg-param-recipient-display-name=quote_if_nam;msg-param-recipient-id=217259245;msg-param-recipient-user-name=quote_if_nam;msg-param-sender-count=1;msg-param-sub-plan-name=Channel\sSubscription\s(nymn_hs);msg-param-sub-plan=1000;room-id=62300805;subscriber=1;system-msg=hyperbolicxd\sgifted\sa\sTier\s1\ssub\sto\squote_if_nam!\sThis\sis\stheir\sfirst\sGift\sSub\sin\sthe\schannel!;tmi-sent-ts=1528190938558;turbo=0;user-id=111534250;user-type= :tmi.twitch.tv USERNOTICE #pajlada)",
// first time sub
R"(@badges=subscriber/0,premium/1;color=#0000FF;display-name=byebyeheart;emotes=;id=fe390424-ab89-4c33-bb5a-53c6e5214b9f;login=byebyeheart;mod=0;msg-id=sub;msg-param-months=0;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=Prime;room-id=39298218;subscriber=0;system-msg=byebyeheart\sjust\ssubscribed\swith\sTwitch\sPrime!;tmi-sent-ts=1528190963670;turbo=0;user-id=131956000;user-type= :tmi.twitch.tv USERNOTICE #pajlada)",
// first time sub
R"(@badges=subscriber/0,premium/1;color=;display-name=vJoeyzz;emotes=;id=b2476df5-fffe-4338-837b-380c5dd90051;login=vjoeyzz;mod=0;msg-id=sub;msg-param-months=0;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=Prime;room-id=39298218;subscriber=0;system-msg=vJoeyzz\sjust\ssubscribed\swith\sTwitch\sPrime!;tmi-sent-ts=1528190995089;turbo=0;user-id=78945903;user-type= :tmi.twitch.tv USERNOTICE #pajlada)",
// first time sub
R"(@badges=subscriber/0,premium/1;color=;display-name=Lennydog3;emotes=;id=44feb1eb-df60-45f6-904b-7bf0d5375a41;login=lennydog3;mod=0;msg-id=sub;msg-param-months=0;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=Prime;room-id=39298218;subscriber=0;system-msg=Lennydog3\sjust\ssubscribed\swith\sTwitch\sPrime!;tmi-sent-ts=1528191098733;turbo=0;user-id=175759335;user-type= :tmi.twitch.tv USERNOTICE #pajlada)",
// resub with message
R"(@badges=subscriber/0,premium/1;color=#1E90FF;display-name=OscarLord;emotes=;id=376529fd-31a8-4da9-9c0d-92a9470da2cd;login=oscarlord;mod=0;msg-id=resub;msg-param-months=2;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=1000;room-id=39298218;subscriber=1;system-msg=OscarLord\sjust\ssubscribed\swith\sa\sTier\s1\ssub.\sOscarLord\ssubscribed\sfor\s2\smonths\sin\sa\srow!;tmi-sent-ts=1528191154801;turbo=0;user-id=162607810;user-type= :tmi.twitch.tv USERNOTICE #pajlada :Hey dk love to watch your streams keep up the good work)",
// resub with message
R"(@badges=subscriber/0,premium/1;color=;display-name=samewl;emotes=9:22-23;id=599fda87-ca1e-41f2-9af7-6a28208daf1c;login=samewl;mod=0;msg-id=resub;msg-param-months=5;msg-param-sub-plan-name=Channel\sSubscription\s(forsenlol);msg-param-sub-plan=Prime;room-id=22484632;subscriber=1;system-msg=samewl\sjust\ssubscribed\swith\sTwitch\sPrime.\ssamewl\ssubscribed\sfor\s5\smonths\sin\sa\srow!;tmi-sent-ts=1528191317948;turbo=0;user-id=70273207;user-type= :tmi.twitch.tv USERNOTICE #pajlada :lot of love sebastian <3)",
// resub without message
R"(@badges=subscriber/12;color=#CC00C2;display-name=cspice;emotes=;id=6fc4c3e0-ca61-454a-84b8-5669dee69fc9;login=cspice;mod=0;msg-id=resub;msg-param-months=12;msg-param-sub-plan-name=Channel\sSubscription\s(forsenlol):\s$9.99\sSub;msg-param-sub-plan=2000;room-id=22484632;subscriber=1;system-msg=cspice\sjust\ssubscribed\swith\sa\sTier\s2\ssub.\scspice\ssubscribed\sfor\s12\smonths\sin\sa\srow!;tmi-sent-ts=1528192510808;turbo=0;user-id=47894662;user-type= :tmi.twitch.tv USERNOTICE #pajlada)",
};
return list;
}
const QStringList &getSampleMiscMessages()
{
static QStringList list{
// display name renders strangely
R"(@badges=;color=#00AD2B;display-name=Iamme420\s;emotes=;id=d47a1e4b-a3c6-4b9e-9bf1-51b8f3dbc76e;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1529670347537;turbo=0;user-id=56422869;user-type= :iamme420!iamme420@iamme420.tmi.twitch.tv PRIVMSG #pajlada :offline chat gachiBASS)",
R"(@badge-info=founder/47;badges=moderator/1,founder/0,premium/1;color=#00FF80;display-name=gempir;emotes=;flags=;id=d4514490-202e-43cb-b429-ef01a9d9c2fe;mod=1;room-id=11148817;subscriber=0;tmi-sent-ts=1575198233854;turbo=0;user-id=77829817;user-type=mod :gempir!gempir@gempir.tmi.twitch.tv PRIVMSG #pajlada :offline chat gachiBASS)",
// "first time chat" message
R"(@badge-info=;badges=glhf-pledge/1;client-nonce=5d2627b0cbe56fa05faf5420def4807d;color=#1E90FF;display-name=oldcoeur;emote-only=1;emotes=84608:0-7;first-msg=1;flags=;id=7412fea4-8683-4cc9-a506-4228127a5c2d;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1623429859222;turbo=0;user-id=139147886;user-type= :oldcoeur!oldcoeur@oldcoeur.tmi.twitch.tv PRIVMSG #pajlada :cmonBruh)",
// Message with founder badge
R"(@badge-info=founder/72;badges=founder/0,bits/5000;color=#FF0000;display-name=TranRed;emotes=;first-msg=0;flags=;id=7482163f-493d-41d9-b36f-fba50e0701b7;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1641123773885;turbo=0;user-id=57019243;user-type= :tranred!tranred@tranred.tmi.twitch.tv PRIVMSG #pajlada :GFMP pajaE)",
// mod announcement
R"(@badge-info=subscriber/47;badges=broadcaster/1,subscriber/3012,twitchconAmsterdam2020/1;color=#FF0000;display-name=Supinic;emotes=;flags=;id=8c26e1ab-b50c-4d9d-bc11-3fd57a941d90;login=supinic;mod=0;msg-id=announcement;msg-param-color=PRIMARY;room-id=31400525;subscriber=1;system-msg=;tmi-sent-ts=1648762219962;user-id=31400525;user-type= :tmi.twitch.tv USERNOTICE #supinic :mm test lol)",
};
return list;
}
const QStringList &getSampleEmoteTestMessages()
{
static QStringList list{
R"(@badge-info=subscriber/3;badges=subscriber/3;color=#0000FF;display-name=Linkoping;emotes=25:40-44;flags=17-26:S.6;id=744f9c58-b180-4f46-bd9e-b515b5ef75c1;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1566335866017;turbo=0;user-id=91673457;user-type= :linkoping!linkoping@linkoping.tmi.twitch.tv PRIVMSG #pajlada :Då kan du begära skadestånd och förtal Kappa)",
R"(@badge-info=subscriber/1;badges=subscriber/0;color=;display-name=jhoelsc;emotes=301683486:46-58,60-72,74-86/301683544:88-100;flags=0-4:S.6;id=1f1afcdd-d94c-4699-b35f-d214deb1e11a;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1588640587462;turbo=0;user-id=505763008;user-type= :jhoelsc!jhoelsc@jhoelsc.tmi.twitch.tv PRIVMSG #pajlada :pensé que no habría directo que bueno que si staryuukiLove staryuukiLove staryuukiLove staryuukiBits)",
R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=41:6-13,16-23;flags=;id=97c28382-e8d2-45a0-bb5d-2305fc4ef139;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922036771;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm, Kreygasm)",
R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=25:24-28/41:6-13,15-22;flags=;id=5a36536b-a952-43f7-9c41-88c829371b7a;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922039721;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm,Kreygasm Kappa (no space then space))",
R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=25:6-10/1902:12-16/88:18-25;flags=;id=aed9e67e-f8cd-493e-aa6b-da054edd7292;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922042881;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kappa.Keepo.PogChamp.extratext (3 emotes with extra text))",
R"(@badge-info=;badges=moderator/1,partner/1;color=#5B99FF;display-name=StreamElements;emotes=86:30-39/822112:73-79;flags=22-27:S.5;id=03c3eec9-afd1-4858-a2e0-fccbf6ad8d1a;mod=1;room-id=11148817;subscriber=0;tmi-sent-ts=1588638345928;turbo=0;user-id=100135110;user-type=mod :streamelements!streamelements@streamelements.tmi.twitch.tv PRIVMSG #pajlada :╔ACTION A LOJA AINDA NÃO ESTÁ PRONTA BibleThump , AGUARDE... NOVIDADES EM BREVE FortOne╔)",
R"(@badge-info=subscriber/20;badges=moderator/1,subscriber/12;color=#19E6E6;display-name=randers;emotes=25:39-43;flags=;id=3ea97f01-abb2-4acf-bdb8-f52e79cd0324;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1588837097115;turbo=0;user-id=40286300;user-type=mod :randers!randers@randers.tmi.twitch.tv PRIVMSG #pajlada :Då kan du begära skadestånd och förtal Kappa)",
R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=41:6-13,15-22;flags=;id=a3196c7e-be4c-4b49-9c5a-8b8302b50c2a;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922213730;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm,Kreygasm (no space))",
};
return list;
}
/// Channel point reward tests
const QString &getSampleChannelRewardMessage()
{
static QString str{
R"({ "type": "MESSAGE", "data": { "topic": "community-points-channel-v1.11148817", "message": { "type": "reward-redeemed", "data": { "timestamp": "2020-07-13T20:19:31.430785354Z", "redemption": { "id": "b9628798-1b4e-4122-b2a6-031658df6755", "user": { "id": "91800084", "login": "cranken1337", "display_name": "cranken1337" }, "channel_id": "11148817", "redeemed_at": "2020-07-13T20:19:31.345237005Z", "reward": { "id": "313969fe-cc9f-4a0a-83c6-172acbd96957", "channel_id": "11148817", "title": "annoying reward pogchamp", "prompt": "", "cost": 3000, "is_user_input_required": true, "is_sub_only": false, "image": null, "default_image": { "url_1x": "https://static-cdn.jtvnw.net/custom-reward-images/default-1.png", "url_2x": "https://static-cdn.jtvnw.net/custom-reward-images/default-2.png", "url_4x": "https://static-cdn.jtvnw.net/custom-reward-images/default-4.png" }, "background_color": "#52ACEC", "is_enabled": true, "is_paused": false, "is_in_stock": true, "max_per_stream": { "is_enabled": false, "max_per_stream": 0 }, "should_redemptions_skip_request_queue": false, "template_id": null, "updated_for_indicator_at": "2020-01-20T04:33:33.624956679Z" }, "user_input": "wow, amazing reward", "status": "UNFULFILLED", "cursor": "Yjk2Mjg3OTgtMWI0ZS00MTIyLWIyYTYtMDMxNjU4ZGY2NzU1X18yMDIwLTA3LTEzVDIwOjE5OjMxLjM0NTIzNzAwNVo=" } } } } })",
};
return str;
}
const QString &getSampleChannelRewardMessage2()
{
static QString str{
R"({ "type": "MESSAGE", "data": { "topic": "community-points-channel-v1.11148817", "message": { "type": "reward-redeemed", "data": { "timestamp": "2020-07-13T20:19:31.430785354Z", "redemption": { "id": "b9628798-1b4e-4122-b2a6-031658df6755", "user": { "id": "91800084", "login": "cranken1337", "display_name": "cranken1337" }, "channel_id": "11148817", "redeemed_at": "2020-07-13T20:19:31.345237005Z", "reward": { "id": "313969fe-cc9f-4a0a-83c6-172acbd96957", "channel_id": "11148817", "title": "annoying reward pogchamp", "prompt": "", "cost": 3000, "is_user_input_required": false, "is_sub_only": false, "image": null, "default_image": { "url_1x": "https://static-cdn.jtvnw.net/custom-reward-images/default-1.png", "url_2x": "https://static-cdn.jtvnw.net/custom-reward-images/default-2.png", "url_4x": "https://static-cdn.jtvnw.net/custom-reward-images/default-4.png" }, "background_color": "#52ACEC", "is_enabled": true, "is_paused": false, "is_in_stock": true, "max_per_stream": { "is_enabled": false, "max_per_stream": 0 }, "should_redemptions_skip_request_queue": false, "template_id": null, "updated_for_indicator_at": "2020-01-20T04:33:33.624956679Z" }, "status": "UNFULFILLED", "cursor": "Yjk2Mjg3OTgtMWI0ZS00MTIyLWIyYTYtMDMxNjU4ZGY2NzU1X18yMDIwLTA3LTEzVDIwOjE5OjMxLjM0NTIzNzAwNVo=" } } } } })",
};
return str;
}
const QString &getSampleChannelRewardIRCMessage()
{
static QString str{
R"(@badge-info=subscriber/43;badges=subscriber/42;color=#1E90FF;custom-reward-id=313969fe-cc9f-4a0a-83c6-172acbd96957;display-name=Cranken1337;emotes=;flags=;id=3cee3f27-a1d0-44d1-a606-722cebdad08b;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1594756484132;turbo=0;user-id=91800084;user-type= :cranken1337!cranken1337@cranken1337.tmi.twitch.tv PRIVMSG #pajlada :wow, amazing reward)",
};
return str;
};
/// Links
const QStringList &getSampleLinkMessages()
{
static QStringList validLinks{
R"(http://github.com/)",
R"(https://github.com/)",
R"(http://username@github.com/)",
R"(https://username@github.com/)",
R"(http://pajlada.github.io)",
R"(https://pajlada.github.io)",
R"(http://pajlada.github.io/)",
R"(https://pajlada.github.io/)",
R"(http://github.com/some/random/path)",
R"(https://github.com/some/random/path)",
R"(http://github.com?query=value)",
R"(https://github.com?query=value)",
R"(http://github.com?query=value&abc=123)",
R"(https://github.com?query=value&abc=123)",
R"(http://github.com/?query=value&abc=123&yhf=abc_def)",
R"(http://github.com/?query=value&abc=123&yhf)",
R"(http://github.com?query=value&abc=)",
R"(http://github.com?query=value&abc=)",
R"(http://github.com/#block)",
R"(https://github.com/#anchor)",
R"(http://github.com/path/?qs=true#block)",
R"(https://github.com/path/?qs=true#anchor)",
R"(github.com/)",
R"(username@github.com/)",
R"(pajlada.github.io)",
R"(pajlada.github.io/)",
R"(github.com/some/random/path)",
R"(github.com?query=value)",
R"(github.com?query=value&abc=123)",
R"(github.com/?query=value&abc=123&yhf=abc_def)",
R"(github.com/?query=value&abc=123&yhf)",
R"(github.com?query=value&abc=)",
R"(github.com?query=value&abc=)",
R"(github.com/#block)",
R"(github.com/path/?qs=true#block)",
R"(HTTP://GITHUB.COM/)",
R"(HTTPS://GITHUB.COM/)",
R"(HTTP://USERNAME@GITHUB.COM/)",
R"(HTTPS://USERNAME@GITHUB.COM/)",
R"(HTTP://PAJLADA.GITHUB.IO)",
R"(HTTPS://PAJLADA.GITHUB.IO)",
R"(HTTP://PAJLADA.GITHUB.IO/)",
R"(HTTPS://PAJLADA.GITHUB.IO/)",
R"(HTTP://GITHUB.COM/SOME/RANDOM/PATH)",
R"(HTTPS://GITHUB.COM/SOME/RANDOM/PATH)",
R"(HTTP://GITHUB.COM?QUERY=VALUE)",
R"(HTTPS://GITHUB.COM?QUERY=VALUE)",
R"(HTTP://GITHUB.COM?QUERY=VALUE&ABC=123)",
R"(HTTPS://GITHUB.COM?QUERY=VALUE&ABC=123)",
R"(HTTP://GITHUB.COM/?QUERY=VALUE&ABC=123&YHF=ABC_DEF)",
R"(HTTP://GITHUB.COM/?QUERY=VALUE&ABC=123&YHF)",
R"(HTTP://GITHUB.COM?QUERY=VALUE&ABC=)",
R"(HTTP://GITHUB.COM?QUERY=VALUE&ABC=)",
R"(HTTP://GITHUB.COM/#BLOCK)",
R"(HTTPS://GITHUB.COM/#ANCHOR)",
R"(HTTP://GITHUB.COM/PATH/?QS=TRUE#BLOCK)",
R"(HTTPS://GITHUB.COM/PATH/?QS=TRUE#ANCHOR)",
R"(GITHUB.COM/)",
R"(USERNAME@GITHUB.COM/)",
R"(PAJLADA.GITHUB.IO)",
R"(PAJLADA.GITHUB.IO/)",
R"(GITHUB.COM/SOME/RANDOM/PATH)",
R"(GITHUB.COM?QUERY=VALUE)",
R"(GITHUB.COM?QUERY=VALUE&ABC=123)",
R"(GITHUB.COM/?QUERY=VALUE&ABC=123&YHF=ABC_DEF)",
R"(GITHUB.COM/?QUERY=VALUE&ABC=123&YHF)",
R"(GITHUB.COM?QUERY=VALUE&ABC=)",
R"(GITHUB.COM?QUERY=VALUE&ABC=)",
R"(GITHUB.COM/#BLOCK)",
R"(GITHUB.COM/PATH/?QS=TRUE#BLOCK)",
R"(http://foo.com/blah_blah)",
R"(http://foo.com/blah_blah/)",
R"(http://foo.com/blah_blah_(wikipedia))",
R"(http://foo.com/blah_blah_(wikipedia)_(again))",
R"(http://www.example.com/wpstyle/?p=364)",
R"(https://www.example.com/foo/?bar=baz&inga=42&quux)",
R"(http://✪df.ws/123)",
R"(http://userid@example.com)",
R"(http://userid@example.com/)",
R"(http://userid@example.com:8080)",
R"(http://userid@example.com:8080/)",
R"(http://142.42.1.1/)",
R"(http://142.42.1.1:8080/)",
R"(http://➡.ws/䨹)",
R"(http://⌘.ws)",
R"(http://⌘.ws/)",
R"(http://foo.com/blah_(wikipedia)#cite-1)",
R"(http://foo.com/blah_(wikipedia)_blah#cite-1)",
R"(http://foo.com/unicode_(✪)_in_parens)",
R"(http://foo.com/(something)?after=parens)",
R"(http://☺.damowmow.com/)",
R"(http://code.google.com/events/#&product=browser)",
R"(http://j.mp)",
R"(ftp://foo.bar/baz)",
R"(http://foo.bar/?q=Test%20URL-encoded%20stuff)",
R"(http://مثال.إختبار)",
R"(http://例子.测试)",
R"(http://उदाहरण.परीक्षा)",
R"(http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com)",
R"(http://1337.net)",
R"(http://a.b-c.de)",
R"(http://223.255.255.254)",
};
static QStringList validButIgnoredLinks{
R"(http://username:password@github.com/)",
R"(https://username:password@github.com/)",
R"(http://userid:password@example.com)",
R"(http://userid:password@example.com/)",
R"(http://userid:password@example.com:8080)",
R"(http://userid:password@example.com:8080/)",
};
static QStringList invalidLinks{
R"(1.40)",
R"(test..)",
R"(test.)",
R"(http://)",
R"(http://.)",
R"(http://..)",
R"(http://../)",
R"(http://?)",
R"(http://??)",
R"(http://??/)",
R"(http://#)",
R"(http://##)",
R"(http://##/)",
R"(http://foo.bar?q=Spaces should be encoded)",
R"(//)",
R"(//a)",
R"(///a)",
R"(///)",
R"(http:///a)",
R"(foo.com)",
R"(rdar://1234)",
R"(h://test)",
R"(http:// shouldfail.com)",
R"(:// should fail)",
R"(http://foo.bar/foo(bar)baz quux)",
R"(ftps://foo.bar/)",
R"(http://-error-.invalid/)",
R"(http://a.b--c.de/)",
R"(http://-a.b.co)",
R"(http://a.b-.co)",
R"(http://0.0.0.0)",
R"(http://10.1.1.0)",
R"(http://10.1.1.255)",
R"(http://224.1.1.1)",
R"(http://1.1.1.1.1)",
R"(http://123.123.123)",
R"(http://3628126748)",
R"(http://.www.foo.bar/)",
R"(http://www.foo.bar./)",
R"(http://.www.foo.bar./)",
R"(http://10.1.1.1)",
};
static QStringList linkList{
R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should pass: )" +
validLinks.join(' '),
R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should NOT pass: )" +
validButIgnoredLinks.join(' '),
R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should technically pass but we choose not to parse them: )" +
invalidLinks.join(' '),
};
return linkList;
};
} // namespace chatterino

24
src/util/SampleData.hpp Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include <QStringList>
namespace chatterino {
/// Sample messages coming from IRC
const QStringList &getSampleCheerMessages();
const QStringList &getSampleSubMessages();
const QStringList &getSampleMiscMessages();
const QStringList &getSampleEmoteTestMessages();
/// Channel point reward tests
const QString &getSampleChannelRewardMessage();
const QString &getSampleChannelRewardMessage2();
const QString &getSampleChannelRewardIRCMessage();
/// Links
const QStringList &getSampleLinkMessages();
} // namespace chatterino

View file

@ -1,174 +0,0 @@
#pragma once
#include <QStringList>
namespace chatterino {
QStringList getValidLinks()
{
return {
R"(http://github.com/)",
R"(https://github.com/)",
R"(http://username@github.com/)",
R"(https://username@github.com/)",
R"(http://pajlada.github.io)",
R"(https://pajlada.github.io)",
R"(http://pajlada.github.io/)",
R"(https://pajlada.github.io/)",
R"(http://github.com/some/random/path)",
R"(https://github.com/some/random/path)",
R"(http://github.com?query=value)",
R"(https://github.com?query=value)",
R"(http://github.com?query=value&abc=123)",
R"(https://github.com?query=value&abc=123)",
R"(http://github.com/?query=value&abc=123&yhf=abc_def)",
R"(http://github.com/?query=value&abc=123&yhf)",
R"(http://github.com?query=value&abc=)",
R"(http://github.com?query=value&abc=)",
R"(http://github.com/#block)",
R"(https://github.com/#anchor)",
R"(http://github.com/path/?qs=true#block)",
R"(https://github.com/path/?qs=true#anchor)",
R"(github.com/)",
R"(username@github.com/)",
R"(pajlada.github.io)",
R"(pajlada.github.io/)",
R"(github.com/some/random/path)",
R"(github.com?query=value)",
R"(github.com?query=value&abc=123)",
R"(github.com/?query=value&abc=123&yhf=abc_def)",
R"(github.com/?query=value&abc=123&yhf)",
R"(github.com?query=value&abc=)",
R"(github.com?query=value&abc=)",
R"(github.com/#block)",
R"(github.com/path/?qs=true#block)",
R"(HTTP://GITHUB.COM/)",
R"(HTTPS://GITHUB.COM/)",
R"(HTTP://USERNAME@GITHUB.COM/)",
R"(HTTPS://USERNAME@GITHUB.COM/)",
R"(HTTP://PAJLADA.GITHUB.IO)",
R"(HTTPS://PAJLADA.GITHUB.IO)",
R"(HTTP://PAJLADA.GITHUB.IO/)",
R"(HTTPS://PAJLADA.GITHUB.IO/)",
R"(HTTP://GITHUB.COM/SOME/RANDOM/PATH)",
R"(HTTPS://GITHUB.COM/SOME/RANDOM/PATH)",
R"(HTTP://GITHUB.COM?QUERY=VALUE)",
R"(HTTPS://GITHUB.COM?QUERY=VALUE)",
R"(HTTP://GITHUB.COM?QUERY=VALUE&ABC=123)",
R"(HTTPS://GITHUB.COM?QUERY=VALUE&ABC=123)",
R"(HTTP://GITHUB.COM/?QUERY=VALUE&ABC=123&YHF=ABC_DEF)",
R"(HTTP://GITHUB.COM/?QUERY=VALUE&ABC=123&YHF)",
R"(HTTP://GITHUB.COM?QUERY=VALUE&ABC=)",
R"(HTTP://GITHUB.COM?QUERY=VALUE&ABC=)",
R"(HTTP://GITHUB.COM/#BLOCK)",
R"(HTTPS://GITHUB.COM/#ANCHOR)",
R"(HTTP://GITHUB.COM/PATH/?QS=TRUE#BLOCK)",
R"(HTTPS://GITHUB.COM/PATH/?QS=TRUE#ANCHOR)",
R"(GITHUB.COM/)",
R"(USERNAME@GITHUB.COM/)",
R"(PAJLADA.GITHUB.IO)",
R"(PAJLADA.GITHUB.IO/)",
R"(GITHUB.COM/SOME/RANDOM/PATH)",
R"(GITHUB.COM?QUERY=VALUE)",
R"(GITHUB.COM?QUERY=VALUE&ABC=123)",
R"(GITHUB.COM/?QUERY=VALUE&ABC=123&YHF=ABC_DEF)",
R"(GITHUB.COM/?QUERY=VALUE&ABC=123&YHF)",
R"(GITHUB.COM?QUERY=VALUE&ABC=)",
R"(GITHUB.COM?QUERY=VALUE&ABC=)",
R"(GITHUB.COM/#BLOCK)",
R"(GITHUB.COM/PATH/?QS=TRUE#BLOCK)",
R"(http://foo.com/blah_blah)",
R"(http://foo.com/blah_blah/)",
R"(http://foo.com/blah_blah_(wikipedia))",
R"(http://foo.com/blah_blah_(wikipedia)_(again))",
R"(http://www.example.com/wpstyle/?p=364)",
R"(https://www.example.com/foo/?bar=baz&inga=42&quux)",
R"(http://✪df.ws/123)",
R"(http://userid@example.com)",
R"(http://userid@example.com/)",
R"(http://userid@example.com:8080)",
R"(http://userid@example.com:8080/)",
R"(http://142.42.1.1/)",
R"(http://142.42.1.1:8080/)",
R"(http://➡.ws/䨹)",
R"(http://⌘.ws)",
R"(http://⌘.ws/)",
R"(http://foo.com/blah_(wikipedia)#cite-1)",
R"(http://foo.com/blah_(wikipedia)_blah#cite-1)",
R"(http://foo.com/unicode_(✪)_in_parens)",
R"(http://foo.com/(something)?after=parens)",
R"(http://☺.damowmow.com/)",
R"(http://code.google.com/events/#&product=browser)",
R"(http://j.mp)",
R"(ftp://foo.bar/baz)",
R"(http://foo.bar/?q=Test%20URL-encoded%20stuff)",
R"(http://مثال.إختبار)",
R"(http://例子.测试)",
R"(http://उदाहरण.परीक्षा)",
R"(http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com)",
R"(http://1337.net)",
R"(http://a.b-c.de)",
R"(http://223.255.255.254)",
};
}
QStringList getValidButIgnoredLinks()
{
return {
R"(http://username:password@github.com/)",
R"(https://username:password@github.com/)",
R"(http://userid:password@example.com)",
R"(http://userid:password@example.com/)",
R"(http://userid:password@example.com:8080)",
R"(http://userid:password@example.com:8080/)",
};
}
QStringList getInvalidLinks()
{
return {
R"(1.40)",
R"(test..)",
R"(test.)",
R"(http://)",
R"(http://.)",
R"(http://..)",
R"(http://../)",
R"(http://?)",
R"(http://??)",
R"(http://??/)",
R"(http://#)",
R"(http://##)",
R"(http://##/)",
R"(http://foo.bar?q=Spaces should be encoded)",
R"(//)",
R"(//a)",
R"(///a)",
R"(///)",
R"(http:///a)",
R"(foo.com)",
R"(rdar://1234)",
R"(h://test)",
R"(http:// shouldfail.com)",
R"(:// should fail)",
R"(http://foo.bar/foo(bar)baz quux)",
R"(ftps://foo.bar/)",
R"(http://-error-.invalid/)",
R"(http://a.b--c.de/)",
R"(http://-a.b.co)",
R"(http://a.b-.co)",
R"(http://0.0.0.0)",
R"(http://10.1.1.0)",
R"(http://10.1.1.255)",
R"(http://224.1.1.1)",
R"(http://1.1.1.1.1)",
R"(http://123.123.123)",
R"(http://3628126748)",
R"(http://.www.foo.bar/)",
R"(http://www.foo.bar./)",
R"(http://.www.foo.bar./)",
R"(http://10.1.1.1)",
};
}
} // namespace chatterino

View file

@ -30,9 +30,11 @@ bool shouldShowWarning = true;
const QStringList &broadcastingBinaries()
{
#ifdef USEWINSDK
static QStringList bins = {"obs.exe", "obs64.exe"};
static QStringList bins = {
"obs.exe", "obs64.exe", "PRISMLiveStudio.exe",
"XSplit.Core.exe", "TwitchStudio.exe", "vMix64.exe"};
#else
static QStringList bins = {"obs"};
static QStringList bins = {"obs", "Twitch Studio", "Streamlabs Desktop"};
#endif
return bins;
}
@ -45,7 +47,7 @@ bool isInStreamerMode()
return true;
case StreamerModeSetting::Disabled:
return false;
case StreamerModeSetting::DetectObs:
case StreamerModeSetting::DetectStreamingSoftware:
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)

View file

@ -4,7 +4,11 @@
namespace chatterino {
enum StreamerModeSetting { Disabled = 0, Enabled = 1, DetectObs = 2 };
enum StreamerModeSetting {
Disabled = 0,
Enabled = 1,
DetectStreamingSoftware = 2,
};
const QStringList &broadcastingBinaries();
bool isInStreamerMode();

View file

@ -1,22 +0,0 @@
#pragma once
#include <algorithm>
namespace chatterino {
namespace util {
template <typename Container, typename UnaryPredicate>
typename Container::iterator find_if(Container &container,
UnaryPredicate pred)
{
return std::find_if(container.begin(), container.end(), pred);
}
template <typename Container, typename UnaryPredicate>
bool any_of(Container &container, UnaryPredicate pred)
{
return std::any_of(container.begin(), container.end(), pred);
}
} // namespace util
} // namespace chatterino

View file

@ -30,8 +30,7 @@
# include <rapidjson/document.h>
# include "providers/twitch/PubSubManager.hpp"
# include "providers/twitch/PubSubMessages.hpp"
# include "util/SampleCheerMessages.hpp"
# include "util/SampleLinks.hpp"
# include "util/SampleData.hpp"
#endif
#include <QApplication>
@ -188,82 +187,16 @@ void Window::addCustomTitlebarButtons()
void Window::addDebugStuff(HotkeyController::HotkeyMap &actions)
{
#ifndef NDEBUG
std::vector<QString> cheerMessages, subMessages, miscMessages, linkMessages,
emoteTestMessages;
cheerMessages = getSampleCheerMessage();
auto validLinks = getValidLinks();
auto invalidLinks = getInvalidLinks();
// clang-format off
subMessages.emplace_back(R"(@badges=staff/1,broadcaster/1,turbo/1;color=#008000;display-name=ronni;emotes=;id=db25007f-7a18-43eb-9379-80131e44d633;login=ronni;mod=0;msg-id=resub;msg-param-months=6;msg-param-sub-plan=Prime;msg-param-sub-plan-name=Prime;room-id=1337;subscriber=1;system-msg=ronni\shas\ssubscribed\sfor\s6\smonths!;tmi-sent-ts=1507246572675;turbo=1;user-id=1337;user-type=staff :tmi.twitch.tv USERNOTICE #pajlada :Great stream -- keep it up!)");
subMessages.emplace_back(R"(@badges=staff/1,premium/1;color=#0000FF;display-name=TWW2;emotes=;id=e9176cd8-5e22-4684-ad40-ce53c2561c5e;login=tww2;mod=0;msg-id=subgift;msg-param-months=1;msg-param-recipient-display-name=Mr_Woodchuck;msg-param-recipient-id=89614178;msg-param-recipient-name=mr_woodchuck;msg-param-sub-plan-name=House\sof\sNyoro~n;msg-param-sub-plan=1000;room-id=19571752;subscriber=0;system-msg=TWW2\sgifted\sa\sTier\s1\ssub\sto\sMr_Woodchuck!;tmi-sent-ts=1521159445153;turbo=0;user-id=13405587;user-type=staff :tmi.twitch.tv USERNOTICE #pajlada)");
// hyperbolicxd gifted a sub to quote_if_nam
subMessages.emplace_back(R"(@badges=subscriber/0,premium/1;color=#00FF7F;display-name=hyperbolicxd;emotes=;id=b20ef4fe-cba8-41d0-a371-6327651dc9cc;login=hyperbolicxd;mod=0;msg-id=subgift;msg-param-months=1;msg-param-recipient-display-name=quote_if_nam;msg-param-recipient-id=217259245;msg-param-recipient-user-name=quote_if_nam;msg-param-sender-count=1;msg-param-sub-plan-name=Channel\sSubscription\s(nymn_hs);msg-param-sub-plan=1000;room-id=62300805;subscriber=1;system-msg=hyperbolicxd\sgifted\sa\sTier\s1\ssub\sto\squote_if_nam!\sThis\sis\stheir\sfirst\sGift\sSub\sin\sthe\schannel!;tmi-sent-ts=1528190938558;turbo=0;user-id=111534250;user-type= :tmi.twitch.tv USERNOTICE #pajlada)");
// first time sub
subMessages.emplace_back(R"(@badges=subscriber/0,premium/1;color=#0000FF;display-name=byebyeheart;emotes=;id=fe390424-ab89-4c33-bb5a-53c6e5214b9f;login=byebyeheart;mod=0;msg-id=sub;msg-param-months=0;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=Prime;room-id=39298218;subscriber=0;system-msg=byebyeheart\sjust\ssubscribed\swith\sTwitch\sPrime!;tmi-sent-ts=1528190963670;turbo=0;user-id=131956000;user-type= :tmi.twitch.tv USERNOTICE #pajlada)");
// first time sub
subMessages.emplace_back(R"(@badges=subscriber/0,premium/1;color=;display-name=vJoeyzz;emotes=;id=b2476df5-fffe-4338-837b-380c5dd90051;login=vjoeyzz;mod=0;msg-id=sub;msg-param-months=0;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=Prime;room-id=39298218;subscriber=0;system-msg=vJoeyzz\sjust\ssubscribed\swith\sTwitch\sPrime!;tmi-sent-ts=1528190995089;turbo=0;user-id=78945903;user-type= :tmi.twitch.tv USERNOTICE #pajlada)");
// first time sub
subMessages.emplace_back(R"(@badges=subscriber/0,premium/1;color=;display-name=Lennydog3;emotes=;id=44feb1eb-df60-45f6-904b-7bf0d5375a41;login=lennydog3;mod=0;msg-id=sub;msg-param-months=0;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=Prime;room-id=39298218;subscriber=0;system-msg=Lennydog3\sjust\ssubscribed\swith\sTwitch\sPrime!;tmi-sent-ts=1528191098733;turbo=0;user-id=175759335;user-type= :tmi.twitch.tv USERNOTICE #pajlada)");
// resub with message
subMessages.emplace_back(R"(@badges=subscriber/0,premium/1;color=#1E90FF;display-name=OscarLord;emotes=;id=376529fd-31a8-4da9-9c0d-92a9470da2cd;login=oscarlord;mod=0;msg-id=resub;msg-param-months=2;msg-param-sub-plan-name=Dakotaz;msg-param-sub-plan=1000;room-id=39298218;subscriber=1;system-msg=OscarLord\sjust\ssubscribed\swith\sa\sTier\s1\ssub.\sOscarLord\ssubscribed\sfor\s2\smonths\sin\sa\srow!;tmi-sent-ts=1528191154801;turbo=0;user-id=162607810;user-type= :tmi.twitch.tv USERNOTICE #pajlada :Hey dk love to watch your streams keep up the good work)");
// resub with message
subMessages.emplace_back(R"(@badges=subscriber/0,premium/1;color=;display-name=samewl;emotes=9:22-23;id=599fda87-ca1e-41f2-9af7-6a28208daf1c;login=samewl;mod=0;msg-id=resub;msg-param-months=5;msg-param-sub-plan-name=Channel\sSubscription\s(forsenlol);msg-param-sub-plan=Prime;room-id=22484632;subscriber=1;system-msg=samewl\sjust\ssubscribed\swith\sTwitch\sPrime.\ssamewl\ssubscribed\sfor\s5\smonths\sin\sa\srow!;tmi-sent-ts=1528191317948;turbo=0;user-id=70273207;user-type= :tmi.twitch.tv USERNOTICE #pajlada :lot of love sebastian <3)");
// resub without message
subMessages.emplace_back(R"(@badges=subscriber/12;color=#CC00C2;display-name=cspice;emotes=;id=6fc4c3e0-ca61-454a-84b8-5669dee69fc9;login=cspice;mod=0;msg-id=resub;msg-param-months=12;msg-param-sub-plan-name=Channel\sSubscription\s(forsenlol):\s$9.99\sSub;msg-param-sub-plan=2000;room-id=22484632;subscriber=1;system-msg=cspice\sjust\ssubscribed\swith\sa\sTier\s2\ssub.\scspice\ssubscribed\sfor\s12\smonths\sin\sa\srow!;tmi-sent-ts=1528192510808;turbo=0;user-id=47894662;user-type= :tmi.twitch.tv USERNOTICE #pajlada)");
// display name renders strangely
miscMessages.emplace_back(R"(@badges=;color=#00AD2B;display-name=Iamme420\s;emotes=;id=d47a1e4b-a3c6-4b9e-9bf1-51b8f3dbc76e;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1529670347537;turbo=0;user-id=56422869;user-type= :iamme420!iamme420@iamme420.tmi.twitch.tv PRIVMSG #pajlada :offline chat gachiBASS)");
miscMessages.emplace_back(R"(@badge-info=founder/47;badges=moderator/1,founder/0,premium/1;color=#00FF80;display-name=gempir;emotes=;flags=;id=d4514490-202e-43cb-b429-ef01a9d9c2fe;mod=1;room-id=11148817;subscriber=0;tmi-sent-ts=1575198233854;turbo=0;user-id=77829817;user-type=mod :gempir!gempir@gempir.tmi.twitch.tv PRIVMSG #pajlada :offline chat gachiBASS)");
// "first time chat" message
miscMessages.emplace_back(R"(@badge-info=;badges=glhf-pledge/1;client-nonce=5d2627b0cbe56fa05faf5420def4807d;color=#1E90FF;display-name=oldcoeur;emote-only=1;emotes=84608:0-7;first-msg=1;flags=;id=7412fea4-8683-4cc9-a506-4228127a5c2d;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1623429859222;turbo=0;user-id=139147886;user-type= :oldcoeur!oldcoeur@oldcoeur.tmi.twitch.tv PRIVMSG #pajlada :cmonBruh)");
// Message with founder badge
miscMessages.emplace_back(R"(@badge-info=founder/72;badges=founder/0,bits/5000;color=#FF0000;display-name=TranRed;emotes=;first-msg=0;flags=;id=7482163f-493d-41d9-b36f-fba50e0701b7;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1641123773885;turbo=0;user-id=57019243;user-type= :tranred!tranred@tranred.tmi.twitch.tv PRIVMSG #pajlada :GFMP pajaE)");
// mod announcement
miscMessages.emplace_back(R"(@badge-info=subscriber/47;badges=broadcaster/1,subscriber/3012,twitchconAmsterdam2020/1;color=#FF0000;display-name=Supinic;emotes=;flags=;id=8c26e1ab-b50c-4d9d-bc11-3fd57a941d90;login=supinic;mod=0;msg-id=announcement;msg-param-color=PRIMARY;room-id=31400525;subscriber=1;system-msg=;tmi-sent-ts=1648762219962;user-id=31400525;user-type= :tmi.twitch.tv USERNOTICE #supinic :mm test lol)");
// various link tests
linkMessages.emplace_back(R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should pass: )" + getValidLinks().join(' '));
linkMessages.emplace_back(R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should NOT pass: )" + getInvalidLinks().join(' '));
linkMessages.emplace_back(R"(@badge-info=subscriber/48;badges=broadcaster/1,subscriber/36,partner/1;color=#CC44FF;display-name=pajlada;emotes=;flags=;id=3c23cf3c-0864-4699-a76b-089350141147;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1577628844607;turbo=0;user-id=11148817;user-type= :pajlada!pajlada@pajlada.tmi.twitch.tv PRIVMSG #pajlada : Links that should technically pass but we choose not to parse them: )" + getValidButIgnoredLinks().join(' '));
// channel point reward test
const char *channelRewardMessage = "{ \"type\": \"MESSAGE\", \"data\": { \"topic\": \"community-points-channel-v1.11148817\", \"message\": { \"type\": \"reward-redeemed\", \"data\": { \"timestamp\": \"2020-07-13T20:19:31.430785354Z\", \"redemption\": { \"id\": \"b9628798-1b4e-4122-b2a6-031658df6755\", \"user\": { \"id\": \"91800084\", \"login\": \"cranken1337\", \"display_name\": \"cranken1337\" }, \"channel_id\": \"11148817\", \"redeemed_at\": \"2020-07-13T20:19:31.345237005Z\", \"reward\": { \"id\": \"313969fe-cc9f-4a0a-83c6-172acbd96957\", \"channel_id\": \"11148817\", \"title\": \"annoying reward pogchamp\", \"prompt\": \"\", \"cost\": 3000, \"is_user_input_required\": true, \"is_sub_only\": false, \"image\": null, \"default_image\": { \"url_1x\": \"https://static-cdn.jtvnw.net/custom-reward-images/default-1.png\", \"url_2x\": \"https://static-cdn.jtvnw.net/custom-reward-images/default-2.png\", \"url_4x\": \"https://static-cdn.jtvnw.net/custom-reward-images/default-4.png\" }, \"background_color\": \"#52ACEC\", \"is_enabled\": true, \"is_paused\": false, \"is_in_stock\": true, \"max_per_stream\": { \"is_enabled\": false, \"max_per_stream\": 0 }, \"should_redemptions_skip_request_queue\": false, \"template_id\": null, \"updated_for_indicator_at\": \"2020-01-20T04:33:33.624956679Z\" }, \"user_input\": \"wow, amazing reward\", \"status\": \"UNFULFILLED\", \"cursor\": \"Yjk2Mjg3OTgtMWI0ZS00MTIyLWIyYTYtMDMxNjU4ZGY2NzU1X18yMDIwLTA3LTEzVDIwOjE5OjMxLjM0NTIzNzAwNVo=\" } } } } }";
const char *channelRewardMessage2 = "{ \"type\": \"MESSAGE\", \"data\": { \"topic\": \"community-points-channel-v1.11148817\", \"message\": { \"type\": \"reward-redeemed\", \"data\": { \"timestamp\": \"2020-07-13T20:19:31.430785354Z\", \"redemption\": { \"id\": \"b9628798-1b4e-4122-b2a6-031658df6755\", \"user\": { \"id\": \"91800084\", \"login\": \"cranken1337\", \"display_name\": \"cranken1337\" }, \"channel_id\": \"11148817\", \"redeemed_at\": \"2020-07-13T20:19:31.345237005Z\", \"reward\": { \"id\": \"313969fe-cc9f-4a0a-83c6-172acbd96957\", \"channel_id\": \"11148817\", \"title\": \"annoying reward pogchamp\", \"prompt\": \"\", \"cost\": 3000, \"is_user_input_required\": false, \"is_sub_only\": false, \"image\": null, \"default_image\": { \"url_1x\": \"https://static-cdn.jtvnw.net/custom-reward-images/default-1.png\", \"url_2x\": \"https://static-cdn.jtvnw.net/custom-reward-images/default-2.png\", \"url_4x\": \"https://static-cdn.jtvnw.net/custom-reward-images/default-4.png\" }, \"background_color\": \"#52ACEC\", \"is_enabled\": true, \"is_paused\": false, \"is_in_stock\": true, \"max_per_stream\": { \"is_enabled\": false, \"max_per_stream\": 0 }, \"should_redemptions_skip_request_queue\": false, \"template_id\": null, \"updated_for_indicator_at\": \"2020-01-20T04:33:33.624956679Z\" }, \"status\": \"UNFULFILLED\", \"cursor\": \"Yjk2Mjg3OTgtMWI0ZS00MTIyLWIyYTYtMDMxNjU4ZGY2NzU1X18yMDIwLTA3LTEzVDIwOjE5OjMxLjM0NTIzNzAwNVo=\" } } } } }";
const char *channelRewardIRCMessage(R"(@badge-info=subscriber/43;badges=subscriber/42;color=#1E90FF;custom-reward-id=313969fe-cc9f-4a0a-83c6-172acbd96957;display-name=Cranken1337;emotes=;flags=;id=3cee3f27-a1d0-44d1-a606-722cebdad08b;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1594756484132;turbo=0;user-id=91800084;user-type= :cranken1337!cranken1337@cranken1337.tmi.twitch.tv PRIVMSG #pajlada :wow, amazing reward)");
emoteTestMessages.emplace_back(R"(@badge-info=subscriber/3;badges=subscriber/3;color=#0000FF;display-name=Linkoping;emotes=25:40-44;flags=17-26:S.6;id=744f9c58-b180-4f46-bd9e-b515b5ef75c1;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1566335866017;turbo=0;user-id=91673457;user-type= :linkoping!linkoping@linkoping.tmi.twitch.tv PRIVMSG #pajlada :Då kan du begära skadestånd och förtal Kappa)");
emoteTestMessages.emplace_back(R"(@badge-info=subscriber/1;badges=subscriber/0;color=;display-name=jhoelsc;emotes=301683486:46-58,60-72,74-86/301683544:88-100;flags=0-4:S.6;id=1f1afcdd-d94c-4699-b35f-d214deb1e11a;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1588640587462;turbo=0;user-id=505763008;user-type= :jhoelsc!jhoelsc@jhoelsc.tmi.twitch.tv PRIVMSG #pajlada :pensé que no habría directo que bueno que si staryuukiLove staryuukiLove staryuukiLove staryuukiBits)");
emoteTestMessages.emplace_back(R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=41:6-13,16-23;flags=;id=97c28382-e8d2-45a0-bb5d-2305fc4ef139;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922036771;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm, Kreygasm)");
emoteTestMessages.emplace_back(R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=25:24-28/41:6-13,15-22;flags=;id=5a36536b-a952-43f7-9c41-88c829371b7a;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922039721;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm,Kreygasm Kappa (no space then space))");
emoteTestMessages.emplace_back(R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=25:6-10/1902:12-16/88:18-25;flags=;id=aed9e67e-f8cd-493e-aa6b-da054edd7292;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922042881;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kappa.Keepo.PogChamp.extratext (3 emotes with extra text))");
emoteTestMessages.emplace_back(R"(@badge-info=;badges=moderator/1,partner/1;color=#5B99FF;display-name=StreamElements;emotes=86:30-39/822112:73-79;flags=22-27:S.5;id=03c3eec9-afd1-4858-a2e0-fccbf6ad8d1a;mod=1;room-id=11148817;subscriber=0;tmi-sent-ts=1588638345928;turbo=0;user-id=100135110;user-type=mod :streamelements!streamelements@streamelements.tmi.twitch.tv PRIVMSG #pajlada :╔ACTION A LOJA AINDA NÃO ESTÁ PRONTA BibleThump , AGUARDE... NOVIDADES EM BREVE FortOne╔)");
emoteTestMessages.emplace_back(R"(@badge-info=subscriber/20;badges=moderator/1,subscriber/12;color=#19E6E6;display-name=randers;emotes=25:39-43;flags=;id=3ea97f01-abb2-4acf-bdb8-f52e79cd0324;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1588837097115;turbo=0;user-id=40286300;user-type=mod :randers!randers@randers.tmi.twitch.tv PRIVMSG #pajlada :Då kan du begära skadestånd och förtal Kappa)");
emoteTestMessages.emplace_back(R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=41:6-13,15-22;flags=;id=a3196c7e-be4c-4b49-9c5a-8b8302b50c2a;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922213730;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm,Kreygasm (no space))");
// clang-format on
actions.emplace("addMiscMessage", [=](std::vector<QString>) -> QString {
const auto &messages = miscMessages;
const auto &messages = getSampleMiscMessages();
static int index = 0;
auto app = getApp();
const auto &msg = messages[index++ % messages.size()];
app->twitch->addFakeMessage(msg);
getApp()->twitch->addFakeMessage(msg);
return "";
});
actions.emplace("addCheerMessage", [=](std::vector<QString>) -> QString {
const auto &messages = cheerMessages;
const auto &messages = getSampleCheerMessages();
static int index = 0;
const auto &msg = messages[index++ % messages.size()];
getApp()->twitch->addFakeMessage(msg);
@ -271,11 +204,10 @@ void Window::addDebugStuff(HotkeyController::HotkeyMap &actions)
});
actions.emplace("addLinkMessage", [=](std::vector<QString>) -> QString {
const auto &messages = linkMessages;
const auto &messages = getSampleLinkMessages();
static int index = 0;
auto app = getApp();
const auto &msg = messages[index++ % messages.size()];
app->twitch->addFakeMessage(msg);
getApp()->twitch->addFakeMessage(msg);
return "";
});
@ -285,19 +217,21 @@ void Window::addDebugStuff(HotkeyController::HotkeyMap &actions)
static bool alt = true;
if (alt)
{
auto oMessage = parsePubSubBaseMessage(channelRewardMessage);
auto oMessage =
parsePubSubBaseMessage(getSampleChannelRewardMessage());
auto oInnerMessage =
oMessage->toInner<PubSubMessageMessage>()
->toInner<PubSubCommunityPointsChannelV1Message>();
app->twitch->addFakeMessage(channelRewardIRCMessage);
app->twitch->addFakeMessage(getSampleChannelRewardIRCMessage());
app->twitch->pubsub->signals_.pointReward.redeemed.invoke(
oInnerMessage->data.value("redemption").toObject());
alt = !alt;
}
else
{
auto oMessage = parsePubSubBaseMessage(channelRewardMessage2);
auto oMessage =
parsePubSubBaseMessage(getSampleChannelRewardMessage2());
auto oInnerMessage =
oMessage->toInner<PubSubMessageMessage>()
->toInner<PubSubCommunityPointsChannelV1Message>();
@ -309,7 +243,7 @@ void Window::addDebugStuff(HotkeyController::HotkeyMap &actions)
});
actions.emplace("addEmoteMessage", [=](std::vector<QString>) -> QString {
const auto &messages = emoteTestMessages;
const auto &messages = getSampleEmoteTestMessages();
static int index = 0;
const auto &msg = messages[index++ % messages.size()];
getApp()->twitch->addFakeMessage(msg);
@ -620,7 +554,7 @@ void Window::addShortcuts()
else if (mode == 3)
{
getSettings()->enableStreamerMode.setValue(
StreamerModeSetting::DetectObs);
StreamerModeSetting::DetectStreamingSoftware);
}
return "";
}},

View file

@ -5,6 +5,7 @@
#include <QCheckBox>
#include <QLabel>
#include <QPushButton>
#include <QScrollArea>
#include <QVBoxLayout>
namespace chatterino {
@ -19,7 +20,15 @@ SelectChannelFiltersDialog::SelectChannelFiltersDialog(
auto okButton = new QPushButton("Ok");
auto cancelButton = new QPushButton("Cancel");
vbox->addLayout(itemVbox);
auto scrollAreaContent = new QWidget;
scrollAreaContent->setLayout(itemVbox);
auto scrollArea = new QScrollArea;
scrollArea->setWidgetResizable(true);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setWidget(scrollAreaContent);
vbox->addWidget(scrollArea);
vbox->addLayout(buttonBox);
buttonBox->addStretch(1);

View file

@ -2119,7 +2119,7 @@ void ChannelView::addCommandExecutionContextMenuItems(
inputText.push_front(cmd.name + " ");
cmdMenu->addAction(cmd.name, [this, inputText] {
cmdMenu->addAction(cmd.name, [this, layout, cmd, inputText] {
ChannelPtr channel;
/* Search popups and user message history's underlyingChannels aren't of type TwitchChannel, but
@ -2132,9 +2132,29 @@ void ChannelView::addCommandExecutionContextMenuItems(
{
channel = this->underlyingChannel_;
}
auto split = dynamic_cast<Split *>(this->parentWidget());
QString userText;
if (split)
{
userText = split->getInput().getInputText();
}
QString value = getApp()->commands->execCustomCommand(
inputText.split(' '), cmd, true, channel,
{
{"user.name", layout->getMessage()->loginName},
{"msg.id", layout->getMessage()->id},
{"msg.text", layout->getMessage()->messageText},
{"input.text", userText},
QString value =
getApp()->commands->execCommand(inputText, channel, false);
// old placeholders
{"user", layout->getMessage()->loginName},
{"msg-id", layout->getMessage()->id},
{"message", layout->getMessage()->messageText},
{"channel", this->channel()->getName()},
});
value = getApp()->commands->execCommand(value, channel, false);
channel->sendMessage(value);
});

View file

@ -81,6 +81,8 @@ public:
void pause(PauseReason reason, boost::optional<uint> msecs = boost::none);
void unpause(PauseReason reason);
MessageElementFlags getFlags() const;
ChannelPtr channel();
void setChannel(ChannelPtr channel_);
@ -158,7 +160,6 @@ private:
void drawMessages(QPainter &painter);
void setSelection(const SelectionItem &start, const SelectionItem &end);
MessageElementFlags getFlags() const;
void selectWholeMessage(MessageLayout *layout, int &messageIndex);
void getWordBounds(MessageLayout *layout,
const MessageLayoutElement *element,

View file

@ -3,11 +3,9 @@
#include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include "common/Channel.hpp"
#include "controllers/hotkeys/HotkeyController.hpp"
#include "messages/Message.hpp"
#include "messages/search/AuthorPredicate.hpp"
#include "messages/search/ChannelPredicate.hpp"
#include "messages/search/LinkPredicate.hpp"
@ -19,8 +17,7 @@
namespace chatterino {
ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName,
const LimitedQueueSnapshot<MessagePtr> &snapshot,
FilterSetPtr filterSet)
const LimitedQueueSnapshot<MessagePtr> &snapshot)
{
ChannelPtr channel(new Channel(channelName, Channel::Type::None));
@ -44,9 +41,6 @@ ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName,
}
}
if (accept && filterSet)
accept = filterSet->filter(message, channel);
// If all predicates match, add the message to the channel
if (accept)
channel->addMessage(message);
@ -67,13 +61,13 @@ void SearchPopup::addShortcuts()
{
HotkeyController::HotkeyMap actions{
{"search",
[this](std::vector<QString>) -> QString {
[this](const std::vector<QString> &) -> QString {
this->searchInput_->setFocus();
this->searchInput_->selectAll();
return "";
}},
{"delete",
[this](std::vector<QString>) -> QString {
[this](const std::vector<QString> &) -> QString {
this->close();
return "";
}},
@ -88,17 +82,25 @@ void SearchPopup::addShortcuts()
HotkeyCategory::PopupWindow, actions, this);
}
void SearchPopup::setChannelFilters(FilterSetPtr filters)
void SearchPopup::addChannel(ChannelView &channel)
{
this->channelFilters_ = std::move(filters);
}
if (this->searchChannels_.empty())
{
this->channelView_->setSourceChannel(channel.channel());
this->channelName_ = channel.channel()->getName();
}
else if (this->searchChannels_.size() == 1)
{
this->channelView_->setSourceChannel(
std::make_shared<Channel>("multichannel", Channel::Type::None));
void SearchPopup::setChannel(const ChannelPtr &channel)
{
this->channelView_->setSourceChannel(channel);
this->channelName_ = channel->getName();
this->snapshot_ = channel->getMessageSnapshot();
this->search();
auto flags = this->channelView_->getFlags();
flags.set(MessageElementFlag::ChannelName);
flags.unset(MessageElementFlag::ModeratorTools);
this->channelView_->setOverrideFlags(flags);
}
this->searchChannels_.append(std::ref(channel));
this->updateWindowTitle();
}
@ -115,6 +117,10 @@ void SearchPopup::updateWindowTitle()
{
historyName = "mentions";
}
else if (this->searchChannels_.size() > 1)
{
historyName = "multiple channels'";
}
else if (this->channelName_.isEmpty())
{
historyName = "<empty>'s";
@ -126,24 +132,90 @@ void SearchPopup::updateWindowTitle()
this->setWindowTitle("Searching in " + historyName + " history");
}
void SearchPopup::showEvent(QShowEvent *)
{
this->search();
}
void SearchPopup::search()
{
if (this->snapshot_.size() == 0)
{
this->snapshot_ = this->buildSnapshot();
}
this->channelView_->setChannel(filter(this->searchInput_->text(),
this->channelName_, this->snapshot_,
this->channelFilters_));
this->channelName_, this->snapshot_));
}
LimitedQueueSnapshot<MessagePtr> SearchPopup::buildSnapshot()
{
// no point in filtering/sorting if it's a single channel search
if (this->searchChannels_.length() == 1)
{
const auto channelPtr = this->searchChannels_.at(0);
return channelPtr.get().channel()->getMessageSnapshot();
}
auto combinedSnapshot = std::vector<std::shared_ptr<const Message>>{};
for (auto &channel : this->searchChannels_)
{
ChannelView &sharedView = channel.get();
const FilterSetPtr filterSet = sharedView.getFilterSet();
const LimitedQueueSnapshot<MessagePtr> &snapshot =
sharedView.channel()->getMessageSnapshot();
// TODO: implement iterator on LimitedQueueSnapshot?
for (auto i = 0; i < snapshot.size(); ++i)
{
const MessagePtr &message = snapshot[i];
if (filterSet && !filterSet->filter(message, sharedView.channel()))
{
continue;
}
combinedSnapshot.push_back(message);
}
}
// remove any duplicate messages from splits containing the same channel
std::sort(combinedSnapshot.begin(), combinedSnapshot.end(),
[](MessagePtr &a, MessagePtr &b) {
return a->id > b->id;
});
auto uniqueIterator =
std::unique(combinedSnapshot.begin(), combinedSnapshot.end(),
[](MessagePtr &a, MessagePtr &b) {
return a->id == b->id;
});
combinedSnapshot.erase(uniqueIterator, combinedSnapshot.end());
// resort by time for presentation
std::sort(combinedSnapshot.begin(), combinedSnapshot.end(),
[](MessagePtr &a, MessagePtr &b) {
return a->serverReceivedTime < b->serverReceivedTime;
});
auto queue = LimitedQueue<MessagePtr>(combinedSnapshot.size());
queue.pushFront(combinedSnapshot);
return queue.getSnapshot();
}
void SearchPopup::initLayout()
{
// VBOX
{
QVBoxLayout *layout1 = new QVBoxLayout(this);
auto *layout1 = new QVBoxLayout(this);
layout1->setMargin(0);
layout1->setSpacing(0);
// HBOX
{
QHBoxLayout *layout2 = new QHBoxLayout(this);
auto *layout2 = new QHBoxLayout(this);
layout2->setMargin(8);
layout2->setSpacing(8);

View file

@ -17,16 +17,17 @@ class SearchPopup : public BasePopup
public:
SearchPopup(QWidget *parent);
virtual void setChannel(const ChannelPtr &channel);
virtual void setChannelFilters(FilterSetPtr filters);
virtual void addChannel(ChannelView &channel);
protected:
virtual void updateWindowTitle();
void showEvent(QShowEvent *event) override;
private:
void initLayout();
void search();
void addShortcuts() override;
LimitedQueueSnapshot<MessagePtr> buildSnapshot();
/**
* @brief Only retains those message from a list of messages that satisfy a
@ -41,8 +42,7 @@ private:
* "snapshot"
*/
static ChannelPtr filter(const QString &text, const QString &channelName,
const LimitedQueueSnapshot<MessagePtr> &snapshot,
FilterSetPtr filterSet);
const LimitedQueueSnapshot<MessagePtr> &snapshot);
/**
* @brief Checks the input for tags and registers their corresponding
@ -58,7 +58,7 @@ private:
QLineEdit *searchInput_{};
ChannelView *channelView_{};
QString channelName_{};
FilterSetPtr channelFilters_;
QList<std::reference_wrapper<ChannelView>> searchChannels_;
};
} // namespace chatterino

View file

@ -336,14 +336,14 @@ void GeneralPage::initLayout(GeneralPageView &layout)
layout.addTitle("Streamer Mode");
layout.addDescription(
"Chatterino can automatically change behavior if it detects that \"OBS "
"Studio\" is running.\nSelect which things you want to change while "
"streaming");
"Chatterino can automatically change behavior if it detects that any "
"streaming software is running.\nSelect which things you want to "
"change while streaming");
ComboBox *dankDropdown =
layout.addDropdown<std::underlying_type<StreamerModeSetting>::type>(
"Enable Streamer Mode",
{"Disabled", "Enabled", "Automatic (Detect OBS)"},
{"Disabled", "Enabled", "Automatic (Detect streaming software)"},
s.enableStreamerMode,
[](int value) {
return value;
@ -352,7 +352,7 @@ void GeneralPage::initLayout(GeneralPageView &layout)
return static_cast<StreamerModeSetting>(args.index);
},
false);
dankDropdown->setMinimumWidth(dankDropdown->minimumSizeHint().width() + 10);
dankDropdown->setMinimumWidth(dankDropdown->minimumSizeHint().width() + 30);
layout.addCheckbox("Hide usercard avatars",
s.streamerModeHideUsercardAvatars);

View file

@ -273,7 +273,12 @@ void Split::addShortcuts()
}},
{"showSearch",
[this](std::vector<QString>) -> QString {
this->showSearch();
this->showSearch(true);
return "";
}},
{"showGlobalSearch",
[this](std::vector<QString>) -> QString {
this->showSearch(false);
return "";
}},
{"reconnect",
@ -985,6 +990,33 @@ void Split::showViewerList()
"viewers"};
auto loadingLabel = new QLabel("Loading...");
searchBar->setPlaceholderText("Search User...");
auto performListSearch = [=]() {
auto query = searchBar->text();
if (query.isEmpty())
{
resultList->hide();
chattersList->show();
return;
}
auto results = chattersList->findItems(query, Qt::MatchContains);
chattersList->hide();
resultList->clear();
for (auto &item : results)
{
if (!item->text().contains("("))
{
resultList->addItem(formatListItemText(item->text()));
}
}
resultList->show();
};
QObject::connect(searchBar, &QLineEdit::textEdited, this,
performListSearch);
NetworkRequest::twitchRequest("https://tmi.twitch.tv/group/user/" +
this->getChannel()->getName() + "/chatters")
.caller(this)
@ -1018,54 +1050,31 @@ void Split::showViewerList()
chattersList->addItem(new QListWidgetItem());
}
performListSearch();
return Success;
})
.execute();
searchBar->setPlaceholderText("Search User...");
QObject::connect(searchBar, &QLineEdit::textEdited, this, [=]() {
auto query = searchBar->text();
if (!query.isEmpty())
{
auto results = chattersList->findItems(query, Qt::MatchContains);
chattersList->hide();
resultList->clear();
for (auto &item : results)
{
if (!labels.contains(item->text()))
{
resultList->addItem(formatListItemText(item->text()));
}
}
resultList->show();
}
else
{
resultList->hide();
chattersList->show();
}
});
QObject::connect(viewerDock, &QDockWidget::topLevelChanged, this, [=]() {
viewerDock->setMinimumWidth(300);
});
auto listDoubleClick = [=](QString userName) {
auto listDoubleClick = [this](const QModelIndex &index) {
const auto itemText = index.data().toString();
// if the list item contains a parentheses it means that
// it's a category label so don't show a usercard
if (!userName.contains("(") && !userName.isEmpty())
if (!itemText.contains("(") && !itemText.isEmpty())
{
this->view_->showUserInfoPopup(userName);
this->view_->showUserInfoPopup(itemText);
}
};
QObject::connect(chattersList, &QListWidget::doubleClicked, this, [=]() {
listDoubleClick(chattersList->currentItem()->text());
});
QObject::connect(chattersList, &QListWidget::doubleClicked, this,
listDoubleClick);
QObject::connect(resultList, &QListWidget::doubleClicked, this, [=]() {
listDoubleClick(resultList->currentItem()->text());
});
QObject::connect(resultList, &QListWidget::doubleClicked, this,
listDoubleClick);
HotkeyController::HotkeyMap actions{
{"delete",
@ -1155,13 +1164,29 @@ const QList<QUuid> Split::getFilters() const
return this->view_->getFilterIds();
}
void Split::showSearch()
void Split::showSearch(bool singleChannel)
{
SearchPopup *popup = new SearchPopup(this);
popup->setChannelFilters(this->view_->getFilterSet());
auto *popup = new SearchPopup(this);
popup->setAttribute(Qt::WA_DeleteOnClose);
popup->setChannel(this->getChannel());
if (singleChannel)
{
popup->addChannel(this->getChannelView());
popup->show();
return;
}
// Pass every ChannelView for every Split across the app to the search popup
auto &notebook = getApp()->windows->getMainWindow().getNotebook();
for (int i = 0; i < notebook.getPageCount(); ++i)
{
auto container = dynamic_cast<SplitContainer *>(notebook.getPageAt(i));
for (auto split : container->getSplits())
{
popup->addChannel(split->getChannelView());
}
}
popup->show();
}

View file

@ -171,7 +171,7 @@ public slots:
void copyToClipboard();
void startWatching();
void setFiltersDialog();
void showSearch();
void showSearch(bool singleChannel);
void showViewerList();
void openSubPage();
void reloadChannelAndSubscriberEmotes();

View file

@ -651,7 +651,8 @@ void SplitInput::insertCompletionText(const QString &input_)
if (done)
{
auto cursor = edit.textCursor();
edit.setText(text.remove(i, position - i + 1).insert(i, input));
edit.setPlainText(
text.remove(i, position - i + 1).insert(i, input));
cursor.setPosition(i + input.size());
edit.setTextCursor(cursor);

View file

@ -17,6 +17,8 @@ set(test_SOURCES
${CMAKE_CURRENT_LIST_DIR}/src/UtilTwitch.cpp
${CMAKE_CURRENT_LIST_DIR}/src/IrcHelpers.cpp
${CMAKE_CURRENT_LIST_DIR}/src/TwitchPubSubClient.cpp
${CMAKE_CURRENT_LIST_DIR}/src/TwitchMessageBuilder.cpp
${CMAKE_CURRENT_LIST_DIR}/src/FormatTime.cpp
# Add your new file above this line!
)

8
tests/README.md Normal file
View file

@ -0,0 +1,8 @@
To run tests all tests you will need to run the `kennethreitz/httpbin` and `ghcr.io/chatterino/twitch-pubsub-server-test:latest` docker images
For example:
```bash
docker run --network=host --detach ghcr.io/chatterino/twitch-pubsub-server-test:latest
docker run -p 9051:80 --detach kennethreitz/httpbin
```

133
tests/src/FormatTime.cpp Normal file
View file

@ -0,0 +1,133 @@
#include "util/FormatTime.hpp"
#include <gtest/gtest.h>
using namespace chatterino;
TEST(FormatTime, Int)
{
struct TestCase {
int input;
QString expectedOutput;
};
std::vector<TestCase> tests{
{
0,
"",
},
{
1337,
"22m 17s",
},
{
623452,
"7d 5h 10m 52s",
},
{
8345,
"2h 19m 5s",
},
{
314034,
"3d 15h 13m 54s",
},
{
27,
"27s",
},
{
34589,
"9h 36m 29s",
},
{
3659,
"1h 59s",
},
{
1045345,
"12d 2h 22m 25s",
},
{
86432,
"1d 32s",
},
};
for (const auto &[input, expected] : tests)
{
const auto actual = formatTime(input);
EXPECT_EQ(actual, expected)
<< qUtf8Printable(actual) << " (" << input
<< ") did not match expected value " << qUtf8Printable(expected);
}
}
TEST(FormatTime, QString)
{
struct TestCase {
QString input;
QString expectedOutput;
};
std::vector<TestCase> tests{
{
"0",
"",
},
{
"1337",
"22m 17s",
},
{
"623452",
"7d 5h 10m 52s",
},
{
"8345",
"2h 19m 5s",
},
{
"314034",
"3d 15h 13m 54s",
},
{
"27",
"27s",
},
{
"34589",
"9h 36m 29s",
},
{
"3659",
"1h 59s",
},
{
"1045345",
"12d 2h 22m 25s",
},
{
"86432",
"1d 32s",
},
{
"",
"n/a",
},
{
"asd",
"n/a",
},
};
for (const auto &[input, expected] : tests)
{
const auto actual = formatTime(input);
EXPECT_EQ(actual, expected)
<< qUtf8Printable(actual) << " (" << qUtf8Printable(input)
<< ") did not match expected value " << qUtf8Printable(expected);
}
}

View file

@ -13,9 +13,17 @@ using namespace chatterino;
namespace {
static QString getStatusURL(int code)
// Change to http://httpbin.org if you don't want to run the docker image yourself to test this
const char *const HTTPBIN_BASE_URL = "http://127.0.0.1:9051";
QString getStatusURL(int code)
{
return QString("http://httpbin.org/status/%1").arg(code);
return QString("%1/status/%2").arg(HTTPBIN_BASE_URL).arg(code);
}
QString getDelayURL(int delay)
{
return QString("%1/delay/%2").arg(HTTPBIN_BASE_URL).arg(delay);
}
} // namespace
@ -201,7 +209,7 @@ TEST(NetworkRequest, TimeoutTimingOut)
{
EXPECT_TRUE(NetworkManager::workerThread.isRunning());
auto url = "http://httpbin.org/delay/5";
auto url = getDelayURL(5);
std::mutex mut;
bool requestDone = false;
@ -248,7 +256,7 @@ TEST(NetworkRequest, TimeoutNotTimingOut)
{
EXPECT_TRUE(NetworkManager::workerThread.isRunning());
auto url = "http://httpbin.org/delay/1";
auto url = getDelayURL(1);
std::mutex mut;
bool requestDone = false;
@ -293,7 +301,7 @@ TEST(NetworkRequest, FinallyCallbackOnTimeout)
{
EXPECT_TRUE(NetworkManager::workerThread.isRunning());
auto url = "http://httpbin.org/delay/5";
auto url = getDelayURL(5);
std::mutex mut;
bool requestDone = false;

View file

@ -0,0 +1,130 @@
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "common/Channel.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/TwitchBadge.hpp"
#include "ircconnection.h"
#include <gtest/gtest.h>
#include <QDebug>
#include <QString>
#include <unordered_map>
#include <vector>
using namespace chatterino;
TEST(TwitchMessageBuilder, CommaSeparatedListTagParsing)
{
struct TestCase {
QString input;
std::pair<QString, QString> expectedOutput;
};
std::vector<TestCase> testCases{
{
"broadcaster/1",
{"broadcaster", "1"},
},
{
"predictions/foo/bar/baz",
{"predictions", "foo/bar/baz"},
},
{
"test/",
{"test", ""},
},
{
"/",
{"", ""},
},
{
"/value",
{"", "value"},
},
{
"",
{"", ""},
},
};
for (const auto &test : testCases)
{
auto output = TwitchMessageBuilder::slashKeyValue(test.input);
EXPECT_EQ(output, test.expectedOutput)
<< "Input " << test.input.toStdString() << " failed";
}
}
TEST(TwitchMessageBuilder, BadgeInfoParsing)
{
struct TestCase {
QByteArray input;
std::unordered_map<QString, QString> expectedBadgeInfo;
std::vector<Badge> expectedBadges;
};
std::vector<TestCase> testCases{
{
R"(@badge-info=predictions/<<<<<<\sHEAD[15A⸝asdf/test;badges=predictions/pink-2;client-nonce=9dbb88e516edf4efb055c011f91ea0cf;color=#FF4500;display-name=もっと頑張って;emotes=;first-msg=0;flags=;id=feb00b12-4ec5-4f77-9160-667de463dab1;mod=0;room-id=99631238;subscriber=0;tmi-sent-ts=1653494874297;turbo=0;user-id=648946956;user-type= :zniksbot!zniksbot@zniksbot.tmi.twitch.tv PRIVMSG #zneix :-tags")",
{
{"predictions", R"(<<<<<<\sHEAD[15A⸝asdf/test)"},
},
{
Badge{"predictions", "pink-2"},
},
},
{
R"(@badge-info=predictions/<<<<<<\sHEAD[15A⸝asdf/test,founder/17;badges=predictions/pink-2,vip/1,founder/0,bits/1;client-nonce=9b836e232170a9df213aefdcb458b67e;color=#696969;display-name=NotKarar;emotes=;first-msg=0;flags=;id=e00881bd-5f21-4993-8bbd-1736cd13d42e;mod=0;room-id=99631238;subscriber=1;tmi-sent-ts=1653494879409;turbo=0;user-id=89954186;user-type= :notkarar!notkarar@notkarar.tmi.twitch.tv PRIVMSG #zneix :-tags)",
{
{"predictions", R"(<<<<<<\sHEAD[15A⸝asdf/test)"},
{"founder", "17"},
},
{
Badge{"predictions", "pink-2"},
Badge{"vip", "1"},
Badge{"founder", "0"},
Badge{"bits", "1"},
},
},
{
R"(@badge-info=predictions/foo/bar/baz;badges=predictions/blue-1,moderator/1,glhf-pledge/1;client-nonce=f73f16228e6e32f8e92b47ab8283b7e1;color=#1E90FF;display-name=zneixbot;emotes=30259:6-12;first-msg=0;flags=;id=9682a5f1-a0b0-45e2-be9f-8074b58c5f8f;mod=1;room-id=99631238;subscriber=0;tmi-sent-ts=1653573594035;turbo=0;user-id=463521670;user-type=mod :zneixbot!zneixbot@zneixbot.tmi.twitch.tv PRIVMSG #zneix :-tags HeyGuys)",
{
{"predictions", "foo/bar/baz"},
},
{
Badge{"predictions", "blue-1"},
Badge{"moderator", "1"},
Badge{"glhf-pledge", "1"},
},
},
{
R"(@badge-info=subscriber/22;badges=broadcaster/1,subscriber/18,glhf-pledge/1;color=#F97304;display-name=zneix;emotes=;first-msg=0;flags=;id=1d99f67f-a566-4416-a4e2-e85d7fce9223;mod=0;room-id=99631238;subscriber=1;tmi-sent-ts=1653612232758;turbo=0;user-id=99631238;user-type= :zneix!zneix@zneix.tmi.twitch.tv PRIVMSG #zneix :-tags)",
{
{"subscriber", "22"},
},
{
Badge{"broadcaster", "1"},
Badge{"subscriber", "18"},
Badge{"glhf-pledge", "1"},
},
},
};
for (const auto &test : testCases)
{
auto privmsg =
Communi::IrcPrivateMessage::fromData(test.input, nullptr);
auto outputBadgeInfo =
TwitchMessageBuilder::parseBadgeInfoTag(privmsg->tags());
EXPECT_EQ(outputBadgeInfo, test.expectedBadgeInfo)
<< "Input for badgeInfo " << test.input.toStdString() << " failed";
auto outputBadges =
SharedMessageBuilder::parseBadgeTag(privmsg->tags());
EXPECT_EQ(outputBadges, test.expectedBadges)
<< "Input for badges " << test.input.toStdString() << " failed";
}
}