test: add snapshot tests for MessageBuilder (#5598)
This commit is contained in:
@@ -497,6 +497,8 @@ set(SOURCE_FILES
|
||||
util/InitUpdateButton.hpp
|
||||
util/IpcQueue.cpp
|
||||
util/IpcQueue.hpp
|
||||
util/IrcHelpers.cpp
|
||||
util/IrcHelpers.hpp
|
||||
util/LayoutHelper.cpp
|
||||
util/LayoutHelper.hpp
|
||||
util/LoadPixmap.cpp
|
||||
@@ -969,6 +971,7 @@ target_compile_definitions(${LIBRARY_PROJECT} PUBLIC
|
||||
IRC_STATIC
|
||||
IRC_NAMESPACE=Communi
|
||||
$<$<BOOL:${WIN32}>:_WIN32_WINNT=0x0A00> # Windows 10
|
||||
$<$<BOOL:${BUILD_TESTS}>:CHATTERINO_WITH_TESTS>
|
||||
)
|
||||
|
||||
if (USE_SYSTEM_QTKEYCHAIN)
|
||||
|
||||
@@ -73,7 +73,6 @@ public:
|
||||
_registerSetting(this->getData());
|
||||
}
|
||||
|
||||
template <typename T2>
|
||||
EnumSetting<Enum> &operator=(Enum newValue)
|
||||
{
|
||||
this->setValue(Underlying(newValue));
|
||||
|
||||
@@ -95,11 +95,11 @@ bool IgnorePhrase::containsEmote() const
|
||||
{
|
||||
if (!this->emotesChecked_)
|
||||
{
|
||||
const auto &accvec = getApp()->getAccounts()->twitch.accounts;
|
||||
for (const auto &acc : accvec)
|
||||
auto accemotes =
|
||||
getApp()->getAccounts()->twitch.getCurrent()->accessEmotes();
|
||||
if (*accemotes)
|
||||
{
|
||||
const auto &accemotes = *acc->accessEmotes();
|
||||
for (const auto &emote : *accemotes)
|
||||
for (const auto &emote : **accemotes)
|
||||
{
|
||||
if (this->replace_.contains(emote.first.string,
|
||||
Qt::CaseSensitive))
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QTimeZone>
|
||||
|
||||
#include <chrono>
|
||||
#include <unordered_set>
|
||||
@@ -1820,10 +1821,12 @@ MessagePtr MessageBuilder::buildHypeChatMessage(
|
||||
|
||||
// actualAmount = amount * 10^(-exponent)
|
||||
double actualAmount = std::pow(10.0, double(-exponent)) * double(amount);
|
||||
subtitle += QLocale::system().toCurrencyString(actualAmount, currency);
|
||||
|
||||
MessageBuilder builder(systemMessage, parseTagString(subtitle),
|
||||
calculateMessageTime(message).time());
|
||||
auto locale = getSystemLocale();
|
||||
subtitle += locale.toCurrencyString(actualAmount, currency);
|
||||
|
||||
auto dt = calculateMessageTime(message);
|
||||
MessageBuilder builder(systemMessage, parseTagString(subtitle), dt.time());
|
||||
builder->flags.set(MessageFlag::ElevatedMessage);
|
||||
return builder.release();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "providers/ffz/FfzBadges.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/network/NetworkRequest.hpp"
|
||||
#include "common/network/NetworkResult.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
@@ -109,4 +110,30 @@ void FfzBadges::load()
|
||||
.execute();
|
||||
}
|
||||
|
||||
void FfzBadges::registerBadge(int badgeID, Badge badge)
|
||||
{
|
||||
assert(getApp()->isTest());
|
||||
|
||||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
this->badges.emplace(badgeID, std::move(badge));
|
||||
}
|
||||
|
||||
void FfzBadges::assignBadgeToUser(const UserId &userID, int badgeID)
|
||||
{
|
||||
assert(getApp()->isTest());
|
||||
|
||||
std::unique_lock lock(this->mutex_);
|
||||
|
||||
auto it = this->userBadges.find(userID.string);
|
||||
if (it != this->userBadges.end())
|
||||
{
|
||||
it->second.emplace(badgeID);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->userBadges.emplace(userID.string, std::set{badgeID});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -30,6 +30,9 @@ public:
|
||||
std::vector<Badge> getUserBadges(const UserId &id);
|
||||
std::optional<Badge> getBadge(int badgeID) const;
|
||||
|
||||
void registerBadge(int badgeID, Badge badge);
|
||||
void assignBadgeToUser(const UserId &userID, int badgeID);
|
||||
|
||||
void load();
|
||||
|
||||
private:
|
||||
|
||||
@@ -632,15 +632,6 @@ std::vector<MessagePtr> parsePrivMessage(Channel *channel,
|
||||
builder.triggerHighlights();
|
||||
}
|
||||
|
||||
if (message->tags().contains(u"pinned-chat-paid-amount"_s))
|
||||
{
|
||||
auto ptr = MessageBuilder::buildHypeChatMessage(message);
|
||||
if (ptr)
|
||||
{
|
||||
builtMessages.emplace_back(std::move(ptr));
|
||||
}
|
||||
}
|
||||
|
||||
return builtMessages;
|
||||
}
|
||||
|
||||
@@ -676,6 +667,11 @@ std::vector<MessagePtr> IrcMessageHandler::parseMessageWithReply(
|
||||
QString content = privMsg->content();
|
||||
int messageOffset = stripLeadingReplyMention(privMsg->tags(), content);
|
||||
MessageParseArgs args;
|
||||
auto tags = privMsg->tags();
|
||||
if (const auto it = tags.find("custom-reward-id"); it != tags.end())
|
||||
{
|
||||
args.channelPointRewardId = it.value().toString();
|
||||
}
|
||||
MessageBuilder builder(channel, message, args, content,
|
||||
privMsg->isAction());
|
||||
builder.setMessageOffset(messageOffset);
|
||||
@@ -688,6 +684,15 @@ std::vector<MessagePtr> IrcMessageHandler::parseMessageWithReply(
|
||||
builder.triggerHighlights();
|
||||
}
|
||||
|
||||
if (message->tags().contains(u"pinned-chat-paid-amount"_s))
|
||||
{
|
||||
auto ptr = MessageBuilder::buildHypeChatMessage(privMsg);
|
||||
if (ptr)
|
||||
{
|
||||
builtMessages.emplace_back(std::move(ptr));
|
||||
}
|
||||
}
|
||||
|
||||
return builtMessages;
|
||||
}
|
||||
|
||||
|
||||
@@ -174,6 +174,17 @@ void TwitchAccount::unblockUser(const QString &userId, const QObject *caller,
|
||||
std::move(onFailure));
|
||||
}
|
||||
|
||||
void TwitchAccount::blockUserLocally(const QString &userID)
|
||||
{
|
||||
assertInGuiThread();
|
||||
assert(getApp()->isTest());
|
||||
|
||||
TwitchUser blockedUser;
|
||||
blockedUser.id = userID;
|
||||
this->ignores_.insert(blockedUser);
|
||||
this->ignoresUserIds_.insert(blockedUser.id);
|
||||
}
|
||||
|
||||
const std::unordered_set<TwitchUser> &TwitchAccount::blocks() const
|
||||
{
|
||||
assertInGuiThread();
|
||||
@@ -336,6 +347,12 @@ SharedAccessGuard<std::shared_ptr<const EmoteMap>> TwitchAccount::accessEmotes()
|
||||
return this->emotes_.accessConst();
|
||||
}
|
||||
|
||||
void TwitchAccount::setEmotes(std::shared_ptr<const EmoteMap> emotes)
|
||||
{
|
||||
assert(getApp()->isTest());
|
||||
*this->emotes_.access() = std::move(emotes);
|
||||
}
|
||||
|
||||
std::optional<EmotePtr> TwitchAccount::twitchEmote(const EmoteName &name) const
|
||||
{
|
||||
auto emotes = this->emotes_.accessConst();
|
||||
|
||||
@@ -71,6 +71,8 @@ public:
|
||||
std::function<void()> onSuccess,
|
||||
std::function<void()> onFailure);
|
||||
|
||||
void blockUserLocally(const QString &userID);
|
||||
|
||||
[[nodiscard]] const std::unordered_set<TwitchUser> &blocks() const;
|
||||
[[nodiscard]] const std::unordered_set<QString> &blockedUserIds() const;
|
||||
|
||||
@@ -83,16 +85,21 @@ public:
|
||||
/// Returns true if the account has access to the given emote set
|
||||
bool hasEmoteSet(const EmoteSetId &id) const;
|
||||
|
||||
/// Return a map of emote sets the account has access to
|
||||
/// Returns a map of emote sets the account has access to
|
||||
///
|
||||
/// Key being the emote set ID, and contents being information about the emote set
|
||||
/// and the emotes contained in the emote set
|
||||
SharedAccessGuard<std::shared_ptr<const TwitchEmoteSetMap>>
|
||||
accessEmoteSets() const;
|
||||
|
||||
/// Return a map of emotes the account has access to
|
||||
/// Returns a map of emotes the account has access to
|
||||
SharedAccessGuard<std::shared_ptr<const EmoteMap>> accessEmotes() const;
|
||||
|
||||
/// Sets the emotes this account has access to
|
||||
///
|
||||
/// This should only be used in tests.
|
||||
void setEmotes(std::shared_ptr<const EmoteMap> emotes);
|
||||
|
||||
/// Return the emote by emote name if the account has access to the emote
|
||||
std::optional<EmotePtr> twitchEmote(const EmoteName &name) const;
|
||||
|
||||
|
||||
@@ -45,10 +45,10 @@ public:
|
||||
|
||||
void loadTwitchBadges();
|
||||
|
||||
private:
|
||||
/// Loads the badges shipped with Chatterino (twitch-badges.json)
|
||||
void loadLocalBadges();
|
||||
|
||||
private:
|
||||
void loaded();
|
||||
void loadEmoteImage(const QString &name, const ImagePtr &image,
|
||||
BadgeIconCallback &&callback);
|
||||
|
||||
@@ -462,6 +462,14 @@ void TwitchChannel::addChannelPointReward(const ChannelPointReward &reward)
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchChannel::addKnownChannelPointReward(const ChannelPointReward &reward)
|
||||
{
|
||||
assert(getApp()->isTest());
|
||||
|
||||
auto channelPointRewards = this->channelPointRewards_.access();
|
||||
channelPointRewards->try_emplace(reward.id, reward);
|
||||
}
|
||||
|
||||
bool TwitchChannel::isChannelPointRewardKnown(const QString &rewardId)
|
||||
{
|
||||
const auto &pointRewards = this->channelPointRewards_.accessConst();
|
||||
@@ -1560,7 +1568,7 @@ void TwitchChannel::refreshBadges()
|
||||
getHelix()->getChannelBadges(
|
||||
this->roomId(),
|
||||
// successCallback
|
||||
[this, weak = weakOf<Channel>(this)](auto channelBadges) {
|
||||
[this, weak = weakOf<Channel>(this)](const auto &channelBadges) {
|
||||
auto shared = weak.lock();
|
||||
if (!shared)
|
||||
{
|
||||
@@ -1568,31 +1576,7 @@ void TwitchChannel::refreshBadges()
|
||||
return;
|
||||
}
|
||||
|
||||
auto badgeSets = this->badgeSets_.access();
|
||||
|
||||
for (const auto &badgeSet : channelBadges.badgeSets)
|
||||
{
|
||||
const auto &setID = badgeSet.setID;
|
||||
for (const auto &version : badgeSet.versions)
|
||||
{
|
||||
auto emote = Emote{
|
||||
.name = EmoteName{},
|
||||
.images =
|
||||
ImageSet{
|
||||
Image::fromUrl(version.imageURL1x, 1,
|
||||
BASE_BADGE_SIZE),
|
||||
Image::fromUrl(version.imageURL2x, .5,
|
||||
BASE_BADGE_SIZE * 2),
|
||||
Image::fromUrl(version.imageURL4x, .25,
|
||||
BASE_BADGE_SIZE * 4),
|
||||
},
|
||||
.tooltip = Tooltip{version.title},
|
||||
.homePage = version.clickURL,
|
||||
};
|
||||
(*badgeSets)[setID][version.id] =
|
||||
std::make_shared<Emote>(emote);
|
||||
}
|
||||
}
|
||||
this->addTwitchBadgeSets(channelBadges);
|
||||
},
|
||||
// failureCallback
|
||||
[this, weak = weakOf<Channel>(this)](auto error, auto message) {
|
||||
@@ -1623,6 +1607,33 @@ void TwitchChannel::refreshBadges()
|
||||
});
|
||||
}
|
||||
|
||||
void TwitchChannel::addTwitchBadgeSets(const HelixChannelBadges &channelBadges)
|
||||
{
|
||||
auto badgeSets = this->badgeSets_.access();
|
||||
|
||||
for (const auto &badgeSet : channelBadges.badgeSets)
|
||||
{
|
||||
const auto &setID = badgeSet.setID;
|
||||
for (const auto &version : badgeSet.versions)
|
||||
{
|
||||
auto emote = Emote{
|
||||
.name = EmoteName{},
|
||||
.images =
|
||||
ImageSet{
|
||||
Image::fromUrl(version.imageURL1x, 1, BASE_BADGE_SIZE),
|
||||
Image::fromUrl(version.imageURL2x, .5,
|
||||
BASE_BADGE_SIZE * 2),
|
||||
Image::fromUrl(version.imageURL4x, .25,
|
||||
BASE_BADGE_SIZE * 4),
|
||||
},
|
||||
.tooltip = Tooltip{version.title},
|
||||
.homePage = version.clickURL,
|
||||
};
|
||||
(*badgeSets)[setID][version.id] = std::make_shared<Emote>(emote);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchChannel::refreshCheerEmotes()
|
||||
{
|
||||
getHelix()->getCheermotes(
|
||||
@@ -1635,74 +1646,75 @@ void TwitchChannel::refreshCheerEmotes()
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<CheerEmoteSet> emoteSets;
|
||||
|
||||
for (const auto &set : cheermoteSets)
|
||||
{
|
||||
auto cheerEmoteSet = CheerEmoteSet();
|
||||
cheerEmoteSet.regex = QRegularExpression(
|
||||
"^" + set.prefix + "([1-9][0-9]*)$",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
for (const auto &tier : set.tiers)
|
||||
{
|
||||
CheerEmote cheerEmote;
|
||||
|
||||
cheerEmote.color = QColor(tier.color);
|
||||
cheerEmote.minBits = tier.minBits;
|
||||
cheerEmote.regex = cheerEmoteSet.regex;
|
||||
|
||||
// TODO(pajlada): We currently hardcode dark here :|
|
||||
// We will continue to do so for now since we haven't had to
|
||||
// solve that anywhere else
|
||||
|
||||
// Combine the prefix (e.g. BibleThump) with the tier (1, 100 etc.)
|
||||
auto emoteTooltip =
|
||||
set.prefix + tier.id + "<br>Twitch Cheer Emote";
|
||||
auto makeImageSet = [](const HelixCheermoteImage &image) {
|
||||
return ImageSet{
|
||||
Image::fromUrl(image.imageURL1x, 1.0,
|
||||
BASE_BADGE_SIZE),
|
||||
Image::fromUrl(image.imageURL2x, 0.5,
|
||||
BASE_BADGE_SIZE * 2),
|
||||
Image::fromUrl(image.imageURL4x, 0.25,
|
||||
BASE_BADGE_SIZE * 4),
|
||||
};
|
||||
};
|
||||
cheerEmote.animatedEmote = std::make_shared<Emote>(Emote{
|
||||
.name = EmoteName{"cheer emote"},
|
||||
.images = makeImageSet(tier.darkAnimated),
|
||||
.tooltip = Tooltip{emoteTooltip},
|
||||
.homePage = Url{},
|
||||
});
|
||||
cheerEmote.staticEmote = std::make_shared<Emote>(Emote{
|
||||
.name = EmoteName{"cheer emote"},
|
||||
.images = makeImageSet(tier.darkStatic),
|
||||
.tooltip = Tooltip{emoteTooltip},
|
||||
.homePage = Url{},
|
||||
});
|
||||
|
||||
cheerEmoteSet.cheerEmotes.emplace_back(
|
||||
std::move(cheerEmote));
|
||||
}
|
||||
|
||||
// Sort cheermotes by cost
|
||||
std::sort(cheerEmoteSet.cheerEmotes.begin(),
|
||||
cheerEmoteSet.cheerEmotes.end(),
|
||||
[](const auto &lhs, const auto &rhs) {
|
||||
return lhs.minBits > rhs.minBits;
|
||||
});
|
||||
|
||||
emoteSets.emplace_back(std::move(cheerEmoteSet));
|
||||
}
|
||||
|
||||
*this->cheerEmoteSets_.access() = std::move(emoteSets);
|
||||
this->setCheerEmoteSets(cheermoteSets);
|
||||
},
|
||||
[] {
|
||||
// Failure
|
||||
});
|
||||
}
|
||||
|
||||
void TwitchChannel::setCheerEmoteSets(
|
||||
const std::vector<HelixCheermoteSet> &cheermoteSets)
|
||||
{
|
||||
std::vector<CheerEmoteSet> emoteSets;
|
||||
|
||||
for (const auto &set : cheermoteSets)
|
||||
{
|
||||
auto cheerEmoteSet = CheerEmoteSet();
|
||||
cheerEmoteSet.regex =
|
||||
QRegularExpression("^" + set.prefix + "([1-9][0-9]*)$",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
for (const auto &tier : set.tiers)
|
||||
{
|
||||
CheerEmote cheerEmote;
|
||||
|
||||
cheerEmote.color = QColor(tier.color);
|
||||
cheerEmote.minBits = tier.minBits;
|
||||
cheerEmote.regex = cheerEmoteSet.regex;
|
||||
|
||||
// TODO(pajlada): We currently hardcode dark here :|
|
||||
// We will continue to do so for now since we haven't had to
|
||||
// solve that anywhere else
|
||||
|
||||
// Combine the prefix (e.g. BibleThump) with the tier (1, 100 etc.)
|
||||
auto emoteTooltip = set.prefix + tier.id + "<br>Twitch Cheer Emote";
|
||||
auto makeImageSet = [](const HelixCheermoteImage &image) {
|
||||
return ImageSet{
|
||||
Image::fromUrl(image.imageURL1x, 1.0, BASE_BADGE_SIZE),
|
||||
Image::fromUrl(image.imageURL2x, 0.5, BASE_BADGE_SIZE * 2),
|
||||
Image::fromUrl(image.imageURL4x, 0.25, BASE_BADGE_SIZE * 4),
|
||||
};
|
||||
};
|
||||
cheerEmote.animatedEmote = std::make_shared<Emote>(Emote{
|
||||
.name = EmoteName{u"cheer emote"_s},
|
||||
.images = makeImageSet(tier.darkAnimated),
|
||||
.tooltip = Tooltip{emoteTooltip},
|
||||
.homePage = Url{},
|
||||
});
|
||||
cheerEmote.staticEmote = std::make_shared<Emote>(Emote{
|
||||
.name = EmoteName{u"cheer emote"_s},
|
||||
.images = makeImageSet(tier.darkStatic),
|
||||
.tooltip = Tooltip{emoteTooltip},
|
||||
.homePage = Url{},
|
||||
});
|
||||
|
||||
cheerEmoteSet.cheerEmotes.emplace_back(std::move(cheerEmote));
|
||||
}
|
||||
|
||||
// Sort cheermotes by cost
|
||||
std::sort(cheerEmoteSet.cheerEmotes.begin(),
|
||||
cheerEmoteSet.cheerEmotes.end(),
|
||||
[](const auto &lhs, const auto &rhs) {
|
||||
return lhs.minBits > rhs.minBits;
|
||||
});
|
||||
|
||||
emoteSets.emplace_back(std::move(cheerEmoteSet));
|
||||
}
|
||||
|
||||
*this->cheerEmoteSets_.access() = std::move(emoteSets);
|
||||
}
|
||||
|
||||
void TwitchChannel::createClip()
|
||||
{
|
||||
if (!this->isLive())
|
||||
@@ -1859,6 +1871,12 @@ std::vector<FfzBadges::Badge> TwitchChannel::ffzChannelBadges(
|
||||
return badges;
|
||||
}
|
||||
|
||||
void TwitchChannel::setFfzChannelBadges(FfzChannelBadgeMap map)
|
||||
{
|
||||
this->tgFfzChannelBadges_.guard();
|
||||
this->ffzChannelBadges_ = std::move(map);
|
||||
}
|
||||
|
||||
std::optional<EmotePtr> TwitchChannel::ffzCustomModBadge() const
|
||||
{
|
||||
return this->ffzCustomModBadge_.get();
|
||||
@@ -1869,6 +1887,16 @@ std::optional<EmotePtr> TwitchChannel::ffzCustomVipBadge() const
|
||||
return this->ffzCustomVipBadge_.get();
|
||||
}
|
||||
|
||||
void TwitchChannel::setFfzCustomModBadge(std::optional<EmotePtr> badge)
|
||||
{
|
||||
this->ffzCustomModBadge_.set(std::move(badge));
|
||||
}
|
||||
|
||||
void TwitchChannel::setFfzCustomVipBadge(std::optional<EmotePtr> badge)
|
||||
{
|
||||
this->ffzCustomVipBadge_.set(std::move(badge));
|
||||
}
|
||||
|
||||
std::optional<CheerEmote> TwitchChannel::cheerEmote(const QString &string) const
|
||||
{
|
||||
auto sets = this->cheerEmoteSets_.access();
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
class TestMessageBuilderP;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class HighlightState;
|
||||
@@ -51,6 +53,9 @@ struct ChannelPointReward;
|
||||
class MessageThread;
|
||||
struct CheerEmoteSet;
|
||||
struct HelixStream;
|
||||
struct HelixCheermoteSet;
|
||||
struct HelixGlobalBadges;
|
||||
using HelixChannelBadges = HelixGlobalBadges;
|
||||
|
||||
class TwitchIrcServer;
|
||||
|
||||
@@ -195,9 +200,15 @@ public:
|
||||
* Returns a list of channel-specific FrankerFaceZ badges for the given user
|
||||
*/
|
||||
std::vector<FfzBadges::Badge> ffzChannelBadges(const QString &userID) const;
|
||||
void setFfzChannelBadges(FfzChannelBadgeMap map);
|
||||
void setFfzCustomModBadge(std::optional<EmotePtr> badge);
|
||||
void setFfzCustomVipBadge(std::optional<EmotePtr> badge);
|
||||
|
||||
void addTwitchBadgeSets(const HelixChannelBadges &channelBadges);
|
||||
|
||||
// Cheers
|
||||
std::optional<CheerEmote> cheerEmote(const QString &string) const;
|
||||
void setCheerEmoteSets(const std::vector<HelixCheermoteSet> &cheermoteSets);
|
||||
|
||||
// Replies
|
||||
/**
|
||||
@@ -243,6 +254,10 @@ public:
|
||||
* This will look at queued up partial messages, and if one is found it will add the queued up partial messages fully hydrated.
|
||||
**/
|
||||
void addChannelPointReward(const ChannelPointReward &reward);
|
||||
/// Adds @a reward to the known rewards
|
||||
///
|
||||
/// Unlike in #addChannelPointReward(), no message will be sent.
|
||||
void addKnownChannelPointReward(const ChannelPointReward &reward);
|
||||
bool isChannelPointRewardKnown(const QString &rewardId);
|
||||
std::optional<ChannelPointReward> channelPointReward(
|
||||
const QString &rewardId) const;
|
||||
@@ -449,6 +464,7 @@ private:
|
||||
friend class MessageBuilder;
|
||||
friend class IrcMessageHandler;
|
||||
friend class Commands_E2E_Test;
|
||||
friend class ::TestMessageBuilderP;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "util/Helpers.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
|
||||
#include <QDirIterator>
|
||||
@@ -301,4 +302,16 @@ QString unescapeZeroWidthJoiner(QString escaped)
|
||||
return escaped;
|
||||
}
|
||||
|
||||
QLocale getSystemLocale()
|
||||
{
|
||||
#ifdef CHATTERINO_WITH_TESTS
|
||||
if (getApp()->isTest())
|
||||
{
|
||||
return {QLocale::English};
|
||||
}
|
||||
#endif
|
||||
|
||||
return QLocale::system();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -189,4 +189,6 @@ constexpr std::optional<std::decay_t<T>> makeConditionedOptional(bool condition,
|
||||
/// a ZWJ. See also: https://github.com/Chatterino/chatterino2/issues/3384.
|
||||
QString unescapeZeroWidthJoiner(QString escaped);
|
||||
|
||||
QLocale getSystemLocale();
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "util/IrcHelpers.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
QDateTime calculateMessageTimeBase(const Communi::IrcMessage *message)
|
||||
{
|
||||
// Check if message is from recent-messages API
|
||||
if (message->tags().contains("historical"))
|
||||
{
|
||||
bool customReceived = false;
|
||||
auto ts =
|
||||
message->tags().value("rm-received-ts").toLongLong(&customReceived);
|
||||
if (!customReceived)
|
||||
{
|
||||
ts = message->tags().value("tmi-sent-ts").toLongLong();
|
||||
}
|
||||
|
||||
return QDateTime::fromMSecsSinceEpoch(ts);
|
||||
}
|
||||
|
||||
// If present, handle tmi-sent-ts tag and use it as timestamp
|
||||
if (message->tags().contains("tmi-sent-ts"))
|
||||
{
|
||||
auto ts = message->tags().value("tmi-sent-ts").toLongLong();
|
||||
return QDateTime::fromMSecsSinceEpoch(ts);
|
||||
}
|
||||
|
||||
// Some IRC Servers might have server-time tag containing UTC date in ISO format, use it as timestamp
|
||||
// See: https://ircv3.net/irc/#server-time
|
||||
if (message->tags().contains("time"))
|
||||
{
|
||||
QString timedate = message->tags().value("time").toString();
|
||||
|
||||
auto date = QDateTime::fromString(timedate, Qt::ISODate);
|
||||
date.setTimeZone(QTimeZone::utc());
|
||||
return date.toLocalTime();
|
||||
}
|
||||
|
||||
// Fallback to current time
|
||||
#ifdef CHATTERINO_WITH_TESTS
|
||||
if (getApp()->isTest())
|
||||
{
|
||||
return QDateTime::fromMSecsSinceEpoch(0, QTimeZone::utc());
|
||||
}
|
||||
#endif
|
||||
|
||||
return QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
QDateTime calculateMessageTime(const Communi::IrcMessage *message)
|
||||
{
|
||||
auto dt = calculateMessageTimeBase(message);
|
||||
|
||||
#ifdef CHATTERINO_WITH_TESTS
|
||||
if (getApp()->isTest())
|
||||
{
|
||||
return dt.toUTC();
|
||||
}
|
||||
#endif
|
||||
|
||||
return dt;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
+1
-37
@@ -59,43 +59,7 @@ inline QString parseTagString(const QString &input)
|
||||
return output;
|
||||
}
|
||||
|
||||
inline QDateTime calculateMessageTime(const Communi::IrcMessage *message)
|
||||
{
|
||||
// Check if message is from recent-messages API
|
||||
if (message->tags().contains("historical"))
|
||||
{
|
||||
bool customReceived = false;
|
||||
auto ts =
|
||||
message->tags().value("rm-received-ts").toLongLong(&customReceived);
|
||||
if (!customReceived)
|
||||
{
|
||||
ts = message->tags().value("tmi-sent-ts").toLongLong();
|
||||
}
|
||||
|
||||
return QDateTime::fromMSecsSinceEpoch(ts);
|
||||
}
|
||||
|
||||
// If present, handle tmi-sent-ts tag and use it as timestamp
|
||||
if (message->tags().contains("tmi-sent-ts"))
|
||||
{
|
||||
auto ts = message->tags().value("tmi-sent-ts").toLongLong();
|
||||
return QDateTime::fromMSecsSinceEpoch(ts);
|
||||
}
|
||||
|
||||
// Some IRC Servers might have server-time tag containing UTC date in ISO format, use it as timestamp
|
||||
// See: https://ircv3.net/irc/#server-time
|
||||
if (message->tags().contains("time"))
|
||||
{
|
||||
QString timedate = message->tags().value("time").toString();
|
||||
|
||||
auto date = QDateTime::fromString(timedate, Qt::ISODate);
|
||||
date.setTimeZone(QTimeZone::utc());
|
||||
return date.toLocalTime();
|
||||
}
|
||||
|
||||
// Fallback to current time
|
||||
return QDateTime::currentDateTime();
|
||||
}
|
||||
QDateTime calculateMessageTime(const Communi::IrcMessage *message);
|
||||
|
||||
// "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:
|
||||
|
||||
Reference in New Issue
Block a user