From e149be38200986d3f256a2e9d5756c0a44f4bd65 Mon Sep 17 00:00:00 2001 From: nerix Date: Sat, 28 Sep 2024 12:40:15 +0200 Subject: [PATCH] feat: include more data when copying messages as JSON (#5600) --- CHANGELOG.md | 1 + src/messages/Emote.cpp | 37 ++++ src/messages/Emote.hpp | 4 + src/messages/ImageSet.cpp | 20 ++ src/messages/ImageSet.hpp | 4 + src/messages/Message.cpp | 78 +++++++ src/messages/Message.hpp | 4 + src/messages/MessageColor.cpp | 17 ++ src/messages/MessageColor.hpp | 3 + src/messages/MessageElement.cpp | 221 ++++++++++++++++++++ src/messages/MessageElement.hpp | 47 ++++- src/messages/MessageThread.cpp | 33 +++ src/messages/MessageThread.hpp | 4 + src/providers/twitch/ChannelPointReward.cpp | 24 +++ src/providers/twitch/ChannelPointReward.hpp | 2 + src/widgets/helper/ChannelView.cpp | 38 +--- 16 files changed, 504 insertions(+), 33 deletions(-) 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)); }); } }