diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f4b479f218..e8c08c93b5f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -90,6 +90,7 @@
- Dev: The timer for `StreamerMode` is now destroyed on the correct thread. (#5571)
- Dev: Cleanup some parts of the `magic_enum` adaptation for Qt. (#5587)
- Dev: Refactored `static`s in headers to only be present once in the final app. (#5588)
+- Dev: The JSON output when copying a message (SHIFT + right-click) is now more extensive. (#5600)
## 2.5.1
diff --git a/src/messages/Emote.cpp b/src/messages/Emote.cpp
index f19fc3027b5..4d7a7bbb90f 100644
--- a/src/messages/Emote.cpp
+++ b/src/messages/Emote.cpp
@@ -1,9 +1,15 @@
#include "messages/Emote.hpp"
+#include "common/Literals.hpp"
+
+#include
+
#include
namespace chatterino {
+using namespace literals;
+
bool operator==(const Emote &a, const Emote &b)
{
return std::tie(a.homePage, a.name, a.tooltip, a.images) ==
@@ -15,6 +21,37 @@ bool operator!=(const Emote &a, const Emote &b)
return !(a == b);
}
+QJsonObject Emote::toJson() const
+{
+ QJsonObject obj{
+ {"name"_L1, this->name.string},
+ {"images"_L1, this->images.toJson()},
+ {"tooltip"_L1, this->tooltip.string},
+ };
+ if (!this->homePage.string.isEmpty())
+ {
+ obj["homePage"_L1] = this->homePage.string;
+ }
+ if (this->zeroWidth)
+ {
+ obj["zeroWidth"_L1] = this->zeroWidth;
+ }
+ if (!this->id.string.isEmpty())
+ {
+ obj["id"_L1] = this->id.string;
+ }
+ if (!this->author.string.isEmpty())
+ {
+ obj["author"_L1] = this->author.string;
+ }
+ if (this->baseName)
+ {
+ obj["baseName"_L1] = this->baseName->string;
+ }
+
+ return obj;
+}
+
EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache)
{
// reuse old shared_ptr if nothing changed
diff --git a/src/messages/Emote.hpp b/src/messages/Emote.hpp
index d0861849bfe..c2fd3885ad1 100644
--- a/src/messages/Emote.hpp
+++ b/src/messages/Emote.hpp
@@ -9,6 +9,8 @@
#include
#include
+class QJsonObject;
+
namespace chatterino {
struct Emote {
@@ -30,6 +32,8 @@ struct Emote {
{
return name.string;
}
+
+ QJsonObject toJson() const;
};
bool operator==(const Emote &a, const Emote &b);
diff --git a/src/messages/ImageSet.cpp b/src/messages/ImageSet.cpp
index 7e25e3f86b6..1c591f64400 100644
--- a/src/messages/ImageSet.cpp
+++ b/src/messages/ImageSet.cpp
@@ -3,6 +3,8 @@
#include "messages/Image.hpp"
#include "singletons/Settings.hpp"
+#include
+
namespace chatterino {
ImageSet::ImageSet()
@@ -135,4 +137,22 @@ bool ImageSet::operator!=(const ImageSet &other) const
return !this->operator==(other);
}
+QJsonObject ImageSet::toJson() const
+{
+ QJsonObject obj;
+ if (!this->imageX1_->isEmpty())
+ {
+ obj[u"1x"] = this->imageX1_->url().string;
+ }
+ if (!this->imageX2_->isEmpty())
+ {
+ obj[u"2x"] = this->imageX2_->url().string;
+ }
+ if (!this->imageX3_->isEmpty())
+ {
+ obj[u"3x"] = this->imageX3_->url().string;
+ }
+ return obj;
+}
+
} // namespace chatterino
diff --git a/src/messages/ImageSet.hpp b/src/messages/ImageSet.hpp
index 49c94eed0cb..c5cba49e2f4 100644
--- a/src/messages/ImageSet.hpp
+++ b/src/messages/ImageSet.hpp
@@ -4,6 +4,8 @@
#include
+class QJsonObject;
+
namespace chatterino {
class Image;
@@ -34,6 +36,8 @@ class ImageSet
bool operator==(const ImageSet &other) const;
bool operator!=(const ImageSet &other) const;
+ QJsonObject toJson() const;
+
private:
ImagePtr imageX1_;
ImagePtr imageX2_;
diff --git a/src/messages/Message.cpp b/src/messages/Message.cpp
index 8840c6919fa..8d3d201bd0a 100644
--- a/src/messages/Message.cpp
+++ b/src/messages/Message.cpp
@@ -1,13 +1,23 @@
#include "messages/Message.hpp"
+#include "Application.hpp"
+#include "common/Literals.hpp"
+#include "messages/MessageThread.hpp"
#include "providers/colors/ColorProvider.hpp"
#include "providers/twitch/TwitchBadge.hpp"
#include "singletons/Settings.hpp"
#include "util/DebugCount.hpp"
+#include "util/QMagicEnum.hpp"
#include "widgets/helper/ScrollbarHighlight.hpp"
+#include
+#include
+#include
+
namespace chatterino {
+using namespace literals;
+
Message::Message()
: parseTime(QTime::currentTime())
{
@@ -80,4 +90,72 @@ ScrollbarHighlight Message::getScrollBarHighlight() const
return {};
}
+QJsonObject Message::toJson() const
+{
+ QJsonObject msg{
+ {"flags"_L1, qmagicenum::enumFlagsName(this->flags.value())},
+ {"id"_L1, this->id},
+ {"searchText"_L1, this->searchText},
+ {"messageText"_L1, this->messageText},
+ {"loginName"_L1, this->loginName},
+ {"displayName"_L1, this->displayName},
+ {"localizedName"_L1, this->localizedName},
+ {"timeoutUser"_L1, this->timeoutUser},
+ {"channelName"_L1, this->channelName},
+ {"usernameColor"_L1, this->usernameColor.name(QColor::HexArgb)},
+ {"count"_L1, static_cast(this->count)},
+ {"serverReceivedTime"_L1,
+ this->serverReceivedTime.toString(Qt::ISODate)},
+ };
+
+ QJsonArray badges;
+ for (const auto &badge : this->badges)
+ {
+ badges.append(badge.key_);
+ }
+ msg["badges"_L1] = badges;
+
+ QJsonObject badgeInfos;
+ for (const auto &[key, value] : this->badgeInfos)
+ {
+ badgeInfos.insert(key, value);
+ }
+ msg["badgeInfos"_L1] = badgeInfos;
+
+ if (this->highlightColor)
+ {
+ msg["highlightColor"_L1] = this->highlightColor->name(QColor::HexArgb);
+ }
+
+ if (this->replyThread)
+ {
+ msg["replyThread"_L1] = this->replyThread->toJson();
+ }
+
+ if (this->replyParent)
+ {
+ msg["replyParent"_L1] = this->replyParent->id;
+ }
+
+ if (this->reward)
+ {
+ msg["reward"_L1] = this->reward->toJson();
+ }
+
+ // XXX: figure out if we can add this in tests
+ if (!getApp()->isTest())
+ {
+ msg["parseTime"_L1] = this->parseTime.toString(Qt::ISODate);
+ }
+
+ QJsonArray elements;
+ for (const auto &element : this->elements)
+ {
+ elements.append(element->toJson());
+ }
+ msg["elements"_L1] = elements;
+
+ return msg;
+}
+
} // namespace chatterino
diff --git a/src/messages/Message.hpp b/src/messages/Message.hpp
index 898f1221789..dd0fa26ff2c 100644
--- a/src/messages/Message.hpp
+++ b/src/messages/Message.hpp
@@ -12,6 +12,8 @@
#include
#include
+class QJsonObject;
+
namespace chatterino {
class MessageElement;
class MessageThread;
@@ -62,6 +64,8 @@ struct Message {
ScrollbarHighlight getScrollBarHighlight() const;
std::shared_ptr reward = nullptr;
+
+ QJsonObject toJson() const;
};
} // namespace chatterino
diff --git a/src/messages/MessageColor.cpp b/src/messages/MessageColor.cpp
index 6e2f01c6737..674f4a6867e 100644
--- a/src/messages/MessageColor.cpp
+++ b/src/messages/MessageColor.cpp
@@ -33,4 +33,21 @@ const QColor &MessageColor::getColor(Theme &themeManager) const
return _default;
}
+QString MessageColor::toString() const
+{
+ switch (this->type_)
+ {
+ case Type::Custom:
+ return this->customColor_.name(QColor::HexArgb);
+ case Type::Text:
+ return QStringLiteral("Text");
+ case Type::System:
+ return QStringLiteral("System");
+ case Type::Link:
+ return QStringLiteral("Link");
+ default:
+ return {};
+ }
+}
+
} // namespace chatterino
diff --git a/src/messages/MessageColor.hpp b/src/messages/MessageColor.hpp
index dd19692c95e..5592b973515 100644
--- a/src/messages/MessageColor.hpp
+++ b/src/messages/MessageColor.hpp
@@ -1,6 +1,7 @@
#pragma once
#include
+#include
namespace chatterino {
class Theme;
@@ -13,6 +14,8 @@ struct MessageColor {
const QColor &getColor(Theme &themeManager) const;
+ QString toString() const;
+
private:
Type type_;
QColor customColor_;
diff --git a/src/messages/MessageElement.cpp b/src/messages/MessageElement.cpp
index a8beed9c011..a4fd6fe846d 100644
--- a/src/messages/MessageElement.cpp
+++ b/src/messages/MessageElement.cpp
@@ -1,6 +1,7 @@
#include "messages/MessageElement.hpp"
#include "Application.hpp"
+#include "common/Literals.hpp"
#include "controllers/moderationactions/ModerationAction.hpp"
#include "debug/Benchmark.hpp"
#include "messages/Emote.hpp"
@@ -14,8 +15,14 @@
#include "util/DebugCount.hpp"
#include "util/Variant.hpp"
+#include
+#include
+#include
+
namespace chatterino {
+using namespace literals;
+
namespace {
// Computes the bounding box for the given vector of images
@@ -88,6 +95,22 @@ void MessageElement::addFlags(MessageElementFlags flags)
this->flags_.set(flags);
}
+QJsonObject MessageElement::toJson() const
+{
+ return {
+ {"trailingSpace"_L1, this->trailingSpace},
+ {
+ "link"_L1,
+ {{
+ {"type"_L1, qmagicenum::enumNameString(this->link_.type)},
+ {"value"_L1, this->link_.value},
+ }},
+ },
+ {"tooltip"_L1, this->tooltip_},
+ {"flags"_L1, qmagicenum::enumFlagsName(this->flags_.value())},
+ };
+}
+
// IMAGE
ImageElement::ImageElement(ImagePtr image, MessageElementFlags flags)
: MessageElement(flags)
@@ -108,6 +131,15 @@ void ImageElement::addToContainer(MessageLayoutContainer &container,
}
}
+QJsonObject ImageElement::toJson() const
+{
+ auto base = MessageElement::toJson();
+ base["type"_L1] = u"ImageElement"_s;
+ base["url"_L1] = this->image_->url().string;
+
+ return base;
+}
+
CircularImageElement::CircularImageElement(ImagePtr image, int padding,
QColor background,
MessageElementFlags flags)
@@ -131,6 +163,17 @@ void CircularImageElement::addToContainer(MessageLayoutContainer &container,
}
}
+QJsonObject CircularImageElement::toJson() const
+{
+ auto base = MessageElement::toJson();
+ base["type"_L1] = u"CircularImageElement"_s;
+ base["url"_L1] = this->image_->url().string;
+ base["padding"_L1] = this->padding_;
+ base["background"_L1] = this->background_.name(QColor::HexArgb);
+
+ return base;
+}
+
// EMOTE
EmoteElement::EmoteElement(const EmotePtr &emote, MessageElementFlags flags,
const MessageColor &textElementColor)
@@ -187,6 +230,19 @@ MessageLayoutElement *EmoteElement::makeImageLayoutElement(
return new ImageLayoutElement(*this, image, size);
}
+QJsonObject EmoteElement::toJson() const
+{
+ auto base = MessageElement::toJson();
+ base["type"_L1] = u"EmoteElement"_s;
+ base["emote"_L1] = this->emote_->toJson();
+ if (this->textElement_)
+ {
+ base["text"_L1] = this->textElement_->toJson();
+ }
+
+ return base;
+}
+
LayeredEmoteElement::LayeredEmoteElement(
std::vector &&emotes, MessageElementFlags flags,
const MessageColor &textElementColor)
@@ -350,6 +406,38 @@ std::vector LayeredEmoteElement::getUniqueEmotes()
return unique;
}
+QJsonObject LayeredEmoteElement::toJson() const
+{
+ auto base = MessageElement::toJson();
+ base["type"_L1] = u"LayeredEmoteElement"_s;
+
+ QJsonArray emotes;
+ for (const auto &emote : this->emotes_)
+ {
+ emotes.append({{
+ {"flags"_L1, qmagicenum::enumFlagsName(emote.flags.value())},
+ {"emote"_L1, emote.ptr->toJson()},
+ }});
+ }
+ base["emotes"_L1] = emotes;
+
+ QJsonArray tooltips;
+ for (const auto &tooltip : this->emoteTooltips_)
+ {
+ emotes.append(tooltip);
+ }
+ base["tooltips"_L1] = tooltips;
+
+ if (this->textElement_)
+ {
+ base["text"_L1] = this->textElement_->toJson();
+ }
+
+ base["textElementColor"_L1] = this->textElementColor_.toString();
+
+ return base;
+}
+
// BADGE
BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags)
: MessageElement(flags)
@@ -390,6 +478,15 @@ MessageLayoutElement *BadgeElement::makeImageLayoutElement(
return element;
}
+QJsonObject BadgeElement::toJson() const
+{
+ auto base = MessageElement::toJson();
+ base["type"_L1] = u"BadgeElement"_s;
+ base["emote"_L1] = this->emote_->toJson();
+
+ return base;
+}
+
// MOD BADGE
ModBadgeElement::ModBadgeElement(const EmotePtr &data,
MessageElementFlags flags_)
@@ -408,6 +505,14 @@ MessageLayoutElement *ModBadgeElement::makeImageLayoutElement(
return element;
}
+QJsonObject ModBadgeElement::toJson() const
+{
+ auto base = BadgeElement::toJson();
+ base["type"_L1] = u"ModBadgeElement"_s;
+
+ return base;
+}
+
// VIP BADGE
VipBadgeElement::VipBadgeElement(const EmotePtr &data,
MessageElementFlags flags_)
@@ -423,6 +528,14 @@ MessageLayoutElement *VipBadgeElement::makeImageLayoutElement(
return element;
}
+QJsonObject VipBadgeElement::toJson() const
+{
+ auto base = BadgeElement::toJson();
+ base["type"_L1] = u"VipBadgeElement"_s;
+
+ return base;
+}
+
// FFZ Badge
FfzBadgeElement::FfzBadgeElement(const EmotePtr &data,
MessageElementFlags flags_, QColor color_)
@@ -440,6 +553,15 @@ MessageLayoutElement *FfzBadgeElement::makeImageLayoutElement(
return element;
}
+QJsonObject FfzBadgeElement::toJson() const
+{
+ auto base = BadgeElement::toJson();
+ base["type"_L1] = u"FfzBadgeElement"_s;
+ base["color"_L1] = this->color.name(QColor::HexArgb);
+
+ return base;
+}
+
// TEXT
TextElement::TextElement(const QString &text, MessageElementFlags flags,
const MessageColor &color, FontStyle style)
@@ -549,6 +671,17 @@ void TextElement::addToContainer(MessageLayoutContainer &container,
}
}
+QJsonObject TextElement::toJson() const
+{
+ auto base = MessageElement::toJson();
+ base["type"_L1] = u"TextElement"_s;
+ base["words"_L1] = QJsonArray::fromStringList(this->words_);
+ base["color"_L1] = this->color_.toString();
+ base["style"_L1] = qmagicenum::enumNameString(this->style_);
+
+ return base;
+}
+
SingleLineTextElement::SingleLineTextElement(const QString &text,
MessageElementFlags flags,
const MessageColor &color,
@@ -677,6 +810,22 @@ void SingleLineTextElement::addToContainer(MessageLayoutContainer &container,
}
}
+QJsonObject SingleLineTextElement::toJson() const
+{
+ auto base = MessageElement::toJson();
+ base["type"_L1] = u"SingleLineTextElement"_s;
+ QJsonArray words;
+ for (const auto &word : this->words_)
+ {
+ words.append(word.text);
+ }
+ base["words"_L1] = words;
+ base["color"_L1] = this->color_.toString();
+ base["style"_L1] = qmagicenum::enumNameString(this->style_);
+
+ return base;
+}
+
LinkElement::LinkElement(const Parsed &parsed, const QString &fullUrl,
MessageElementFlags flags, const MessageColor &color,
FontStyle style)
@@ -701,6 +850,17 @@ Link LinkElement::getLink() const
return {Link::Url, this->linkInfo_.url()};
}
+QJsonObject LinkElement::toJson() const
+{
+ auto base = TextElement::toJson();
+ base["type"_L1] = u"LinkElement"_s;
+ base["link"_L1] = this->linkInfo_.originalUrl();
+ base["lowercase"_L1] = QJsonArray::fromStringList(this->lowercase_);
+ base["original"_L1] = QJsonArray::fromStringList(this->original_);
+
+ return base;
+}
+
MentionElement::MentionElement(const QString &displayName, QString loginName_,
MessageColor fallbackColor_,
MessageColor userColor_)
@@ -756,7 +916,24 @@ Link MentionElement::getLink() const
return {Link::UserInfo, this->userLoginName};
}
+QJsonObject MentionElement::toJson() const
+{
+ auto base = TextElement::toJson();
+ base["type"_L1] = u"MentionElement"_s;
+ base["fallbackColor"_L1] = this->fallbackColor.toString();
+ base["userColor"_L1] = this->userColor.toString();
+ base["userLoginName"_L1] = this->userLoginName;
+
+ return base;
+}
+
// TIMESTAMP
+TimestampElement::TimestampElement()
+ : TimestampElement(getApp()->isTest() ? QTime::fromMSecsSinceStartOfDay(0)
+ : QTime::currentTime())
+{
+}
+
TimestampElement::TimestampElement(QTime time)
: MessageElement(MessageElementFlag::Timestamp)
, time_(time)
@@ -790,6 +967,17 @@ TextElement *TimestampElement::formatTime(const QTime &time)
MessageColor::System, FontStyle::ChatMedium);
}
+QJsonObject TimestampElement::toJson() const
+{
+ auto base = MessageElement::toJson();
+ base["type"_L1] = u"TimestampElement"_s;
+ base["time"_L1] = this->time_.toString(Qt::ISODate);
+ base["element"_L1] = this->element_->toJson();
+ base["format"_L1] = this->format_;
+
+ return base;
+}
+
// TWITCH MODERATION
TwitchModerationElement::TwitchModerationElement()
: MessageElement(MessageElementFlag::ModeratorTools)
@@ -824,6 +1012,14 @@ void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
}
}
+QJsonObject TwitchModerationElement::toJson() const
+{
+ auto base = MessageElement::toJson();
+ base["type"_L1] = u"TwitchModerationElement"_s;
+
+ return base;
+}
+
LinebreakElement::LinebreakElement(MessageElementFlags flags)
: MessageElement(flags)
{
@@ -838,6 +1034,14 @@ void LinebreakElement::addToContainer(MessageLayoutContainer &container,
}
}
+QJsonObject LinebreakElement::toJson() const
+{
+ auto base = MessageElement::toJson();
+ base["type"_L1] = u"LinebreakElement"_s;
+
+ return base;
+}
+
ScalingImageElement::ScalingImageElement(ImageSet images,
MessageElementFlags flags)
: MessageElement(flags)
@@ -864,6 +1068,15 @@ void ScalingImageElement::addToContainer(MessageLayoutContainer &container,
}
}
+QJsonObject ScalingImageElement::toJson() const
+{
+ auto base = MessageElement::toJson();
+ base["type"_L1] = u"ScalingImageElement"_s;
+ base["image"_L1] = this->images_.getImage1()->url().string;
+
+ return base;
+}
+
ReplyCurveElement::ReplyCurveElement()
: MessageElement(MessageElementFlag::RepliedMessage)
{
@@ -886,4 +1099,12 @@ void ReplyCurveElement::addToContainer(MessageLayoutContainer &container,
}
}
+QJsonObject ReplyCurveElement::toJson() const
+{
+ auto base = MessageElement::toJson();
+ base["type"_L1] = u"ReplyCurveElement"_s;
+
+ return base;
+}
+
} // namespace chatterino
diff --git a/src/messages/MessageElement.hpp b/src/messages/MessageElement.hpp
index 49ce762cb91..041593cac20 100644
--- a/src/messages/MessageElement.hpp
+++ b/src/messages/MessageElement.hpp
@@ -7,6 +7,7 @@
#include "providers/links/LinkInfo.hpp"
#include "singletons/Fonts.hpp"
+#include
#include
#include
#include
@@ -16,6 +17,8 @@
#include
#include
+class QJsonObject;
+
namespace chatterino {
class Channel;
struct MessageLayoutContainer;
@@ -183,6 +186,8 @@ class MessageElement
virtual void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) = 0;
+ virtual QJsonObject toJson() const;
+
protected:
MessageElement(MessageElementFlags flags);
bool trailingSpace = true;
@@ -202,6 +207,8 @@ class ImageElement : public MessageElement
void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override;
+ QJsonObject toJson() const override;
+
private:
ImagePtr image_;
};
@@ -216,6 +223,8 @@ class CircularImageElement : public MessageElement
void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override;
+ QJsonObject toJson() const override;
+
private:
ImagePtr image_;
int padding_;
@@ -234,6 +243,8 @@ class TextElement : public MessageElement
void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override;
+ QJsonObject toJson() const override;
+
protected:
QStringList words_;
@@ -253,6 +264,8 @@ class SingleLineTextElement : public MessageElement
void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override;
+ QJsonObject toJson() const override;
+
private:
MessageColor color_;
FontStyle style_;
@@ -294,6 +307,8 @@ class LinkElement : public TextElement
return &this->linkInfo_;
}
+ QJsonObject toJson() const override;
+
private:
LinkInfo linkInfo_;
// these are implicitly shared
@@ -328,6 +343,8 @@ class MentionElement : public TextElement
MessageElement *setLink(const Link &link) override;
Link getLink() const override;
+ QJsonObject toJson() const override;
+
private:
/**
* The color of the element in case the "Colorize @usernames" is disabled
@@ -355,6 +372,8 @@ class EmoteElement : public MessageElement
MessageElementFlags flags_) override;
EmotePtr getEmote() const;
+ QJsonObject toJson() const override;
+
protected:
virtual MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
const QSize &size);
@@ -390,6 +409,8 @@ class LayeredEmoteElement : public MessageElement
std::vector getUniqueEmotes() const;
const std::vector &getEmoteTooltips() const;
+ QJsonObject toJson() const override;
+
private:
MessageLayoutElement *makeImageLayoutElement(
const std::vector &image, const std::vector &sizes,
@@ -416,6 +437,8 @@ class BadgeElement : public MessageElement
EmotePtr getEmote() const;
+ QJsonObject toJson() const override;
+
protected:
virtual MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
const QSize &size);
@@ -429,6 +452,8 @@ class ModBadgeElement : public BadgeElement
public:
ModBadgeElement(const EmotePtr &data, MessageElementFlags flags_);
+ QJsonObject toJson() const override;
+
protected:
MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
const QSize &size) override;
@@ -439,6 +464,8 @@ class VipBadgeElement : public BadgeElement
public:
VipBadgeElement(const EmotePtr &data, MessageElementFlags flags_);
+ QJsonObject toJson() const override;
+
protected:
MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
const QSize &size) override;
@@ -450,6 +477,8 @@ class FfzBadgeElement : public BadgeElement
FfzBadgeElement(const EmotePtr &data, MessageElementFlags flags_,
QColor color_);
+ QJsonObject toJson() const override;
+
protected:
MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
const QSize &size) override;
@@ -460,7 +489,8 @@ class FfzBadgeElement : public BadgeElement
class TimestampElement : public MessageElement
{
public:
- TimestampElement(QTime time_ = QTime::currentTime());
+ TimestampElement();
+ TimestampElement(QTime time_);
~TimestampElement() override = default;
void addToContainer(MessageLayoutContainer &container,
@@ -468,6 +498,8 @@ class TimestampElement : public MessageElement
TextElement *formatTime(const QTime &time);
+ QJsonObject toJson() const override;
+
private:
QTime time_;
std::unique_ptr element_;
@@ -483,6 +515,8 @@ class TwitchModerationElement : public MessageElement
void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override;
+
+ QJsonObject toJson() const override;
};
// Forces a linebreak
@@ -493,6 +527,8 @@ class LinebreakElement : public MessageElement
void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override;
+
+ QJsonObject toJson() const override;
};
// Image element which will pick the quality of the image based on ui scale
@@ -504,6 +540,8 @@ class ScalingImageElement : public MessageElement
void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override;
+ QJsonObject toJson() const override;
+
private:
ImageSet images_;
};
@@ -515,6 +553,13 @@ class ReplyCurveElement : public MessageElement
void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override;
+
+ QJsonObject toJson() const override;
};
} // namespace chatterino
+
+template <>
+struct magic_enum::customize::enum_range {
+ static constexpr bool is_flags = true; // NOLINT(readability-identifier-*)
+};
diff --git a/src/messages/MessageThread.cpp b/src/messages/MessageThread.cpp
index e1227ab09e0..c59a3a26a03 100644
--- a/src/messages/MessageThread.cpp
+++ b/src/messages/MessageThread.cpp
@@ -1,12 +1,20 @@
#include "messages/MessageThread.hpp"
+#include "common/Literals.hpp"
#include "messages/Message.hpp"
#include "util/DebugCount.hpp"
+#include "util/QMagicEnum.hpp"
+
+#include
+#include
+#include
#include
namespace chatterino {
+using namespace literals;
+
MessageThread::MessageThread(std::shared_ptr rootMessage)
: rootMessageId_(rootMessage->id)
, rootMessage_(std::move(rootMessage))
@@ -80,4 +88,29 @@ void MessageThread::markUnsubscribed()
this->subscriptionUpdated();
}
+QJsonObject MessageThread::toJson() const
+{
+ QJsonObject obj{
+ {"rootId"_L1, this->rootMessageId_},
+ {"subscription"_L1, qmagicenum::enumNameString(this->subscription_)},
+ };
+
+ QJsonArray replies;
+ for (const auto &msg : this->replies_)
+ {
+ auto locked = msg.lock();
+ if (locked)
+ {
+ replies.append(locked->id);
+ }
+ else
+ {
+ replies.append(QJsonValue::Null);
+ }
+ }
+ obj["replies"_L1] = replies;
+
+ return obj;
+}
+
} // namespace chatterino
diff --git a/src/messages/MessageThread.hpp b/src/messages/MessageThread.hpp
index 442db46a67d..56b088c0f60 100644
--- a/src/messages/MessageThread.hpp
+++ b/src/messages/MessageThread.hpp
@@ -6,6 +6,8 @@
#include
#include
+class QJsonObject;
+
namespace chatterino {
struct Message;
@@ -62,6 +64,8 @@ class MessageThread
return replies_;
}
+ QJsonObject toJson() const;
+
boost::signals2::signal subscriptionUpdated;
private:
diff --git a/src/providers/twitch/ChannelPointReward.cpp b/src/providers/twitch/ChannelPointReward.cpp
index 8849b8b62a0..0d4ab132b95 100644
--- a/src/providers/twitch/ChannelPointReward.cpp
+++ b/src/providers/twitch/ChannelPointReward.cpp
@@ -1,5 +1,6 @@
#include "providers/twitch/ChannelPointReward.hpp"
+#include "common/Literals.hpp"
#include "messages/Image.hpp"
#include
@@ -15,6 +16,8 @@ QString twitchChannelPointRewardUrl(const QString &file)
namespace chatterino {
+using namespace literals;
+
ChannelPointReward::ChannelPointReward(const QJsonObject &redemption)
{
auto reward = redemption.value("reward").toObject();
@@ -113,4 +116,25 @@ ChannelPointReward::ChannelPointReward(const QJsonObject &redemption)
}
}
+QJsonObject ChannelPointReward::toJson() const
+{
+ return {
+ {"id"_L1, this->id},
+ {"channelId"_L1, this->channelId},
+ {"title"_L1, this->title},
+ {"cost"_L1, this->cost},
+ {"image"_L1, this->image.toJson()},
+ {"isUserInputRequired"_L1, this->isUserInputRequired},
+ {"isBits"_L1, this->isBits},
+ {"emoteId"_L1, this->emoteId},
+ {"emoteName"_L1, this->emoteName},
+ {"user"_L1,
+ {{
+ {"id"_L1, this->user.id},
+ {"login"_L1, this->user.login},
+ {"displayName"_L1, this->user.displayName},
+ }}},
+ };
+}
+
} // namespace chatterino
diff --git a/src/providers/twitch/ChannelPointReward.hpp b/src/providers/twitch/ChannelPointReward.hpp
index 6fdd985b6e7..9015adfc83a 100644
--- a/src/providers/twitch/ChannelPointReward.hpp
+++ b/src/providers/twitch/ChannelPointReward.hpp
@@ -24,6 +24,8 @@ struct ChannelPointReward {
QString login;
QString displayName;
} user;
+
+ QJsonObject toJson() const;
};
} // namespace chatterino
diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp
index ab0e632260c..026af4c1061 100644
--- a/src/widgets/helper/ChannelView.cpp
+++ b/src/widgets/helper/ChannelView.cpp
@@ -256,40 +256,14 @@ void addHiddenContextMenuItems(QMenu *menu,
});
}
- const auto *message = layout->getMessage();
+ auto message = layout->getMessagePtr();
- if (message != nullptr)
+ if (message)
{
- QJsonDocument jsonDocument;
-
- QJsonObject jsonObject;
-
- jsonObject["id"] = message->id;
- jsonObject["searchText"] = message->searchText;
- jsonObject["messageText"] = message->messageText;
- jsonObject["flags"] = qmagicenum::enumFlagsName(message->flags.value());
- if (message->reward)
- {
- QJsonObject reward;
- reward["id"] = message->reward->id;
- reward["title"] = message->reward->title;
- reward["cost"] = message->reward->cost;
- reward["isUserInputRequired"] =
- message->reward->isUserInputRequired;
- jsonObject["reward"] = reward;
- }
- else
- {
- jsonObject["reward"] = QJsonValue();
- }
-
- jsonDocument.setObject(jsonObject);
-
- auto jsonString =
- jsonDocument.toJson(QJsonDocument::JsonFormat::Indented);
-
- menu->addAction("Copy message &JSON", [jsonString] {
- crossPlatformCopy(jsonString);
+ menu->addAction("Copy message &JSON", [message] {
+ auto jsonString = QJsonDocument{message->toJson()}.toJson(
+ QJsonDocument::Indented);
+ crossPlatformCopy(QString::fromUtf8(jsonString));
});
}
}