feat: include more data when copying messages as JSON (#5600)

This commit is contained in:
nerix 2024-09-28 12:40:15 +02:00 committed by GitHub
parent edf18a7a0f
commit e149be3820
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 504 additions and 33 deletions

View file

@ -90,6 +90,7 @@
- Dev: The timer for `StreamerMode` is now destroyed on the correct thread. (#5571) - 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: 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: Refactored `static`s in headers to only be present once in the final app. (#5588)
- Dev: The JSON output when copying a message (<kbd>SHIFT</kbd> + right-click) is now more extensive. (#5600)
## 2.5.1 ## 2.5.1

View file

@ -1,9 +1,15 @@
#include "messages/Emote.hpp" #include "messages/Emote.hpp"
#include "common/Literals.hpp"
#include <QJsonObject>
#include <unordered_map> #include <unordered_map>
namespace chatterino { namespace chatterino {
using namespace literals;
bool operator==(const Emote &a, const Emote &b) bool operator==(const Emote &a, const Emote &b)
{ {
return std::tie(a.homePage, a.name, a.tooltip, a.images) == 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); 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) EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache)
{ {
// reuse old shared_ptr if nothing changed // reuse old shared_ptr if nothing changed

View file

@ -9,6 +9,8 @@
#include <optional> #include <optional>
#include <unordered_map> #include <unordered_map>
class QJsonObject;
namespace chatterino { namespace chatterino {
struct Emote { struct Emote {
@ -30,6 +32,8 @@ struct Emote {
{ {
return name.string; return name.string;
} }
QJsonObject toJson() const;
}; };
bool operator==(const Emote &a, const Emote &b); bool operator==(const Emote &a, const Emote &b);

View file

@ -3,6 +3,8 @@
#include "messages/Image.hpp" #include "messages/Image.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include <QJsonObject>
namespace chatterino { namespace chatterino {
ImageSet::ImageSet() ImageSet::ImageSet()
@ -135,4 +137,22 @@ bool ImageSet::operator!=(const ImageSet &other) const
return !this->operator==(other); 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 } // namespace chatterino

View file

@ -4,6 +4,8 @@
#include <memory> #include <memory>
class QJsonObject;
namespace chatterino { namespace chatterino {
class Image; class Image;
@ -34,6 +36,8 @@ public:
bool operator==(const ImageSet &other) const; bool operator==(const ImageSet &other) const;
bool operator!=(const ImageSet &other) const; bool operator!=(const ImageSet &other) const;
QJsonObject toJson() const;
private: private:
ImagePtr imageX1_; ImagePtr imageX1_;
ImagePtr imageX2_; ImagePtr imageX2_;

View file

@ -1,13 +1,23 @@
#include "messages/Message.hpp" #include "messages/Message.hpp"
#include "Application.hpp"
#include "common/Literals.hpp"
#include "messages/MessageThread.hpp"
#include "providers/colors/ColorProvider.hpp" #include "providers/colors/ColorProvider.hpp"
#include "providers/twitch/TwitchBadge.hpp" #include "providers/twitch/TwitchBadge.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include "util/DebugCount.hpp" #include "util/DebugCount.hpp"
#include "util/QMagicEnum.hpp"
#include "widgets/helper/ScrollbarHighlight.hpp" #include "widgets/helper/ScrollbarHighlight.hpp"
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
namespace chatterino { namespace chatterino {
using namespace literals;
Message::Message() Message::Message()
: parseTime(QTime::currentTime()) : parseTime(QTime::currentTime())
{ {
@ -80,4 +90,72 @@ ScrollbarHighlight Message::getScrollBarHighlight() const
return {}; 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<qint64>(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 } // namespace chatterino

View file

@ -12,6 +12,8 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
class QJsonObject;
namespace chatterino { namespace chatterino {
class MessageElement; class MessageElement;
class MessageThread; class MessageThread;
@ -62,6 +64,8 @@ struct Message {
ScrollbarHighlight getScrollBarHighlight() const; ScrollbarHighlight getScrollBarHighlight() const;
std::shared_ptr<ChannelPointReward> reward = nullptr; std::shared_ptr<ChannelPointReward> reward = nullptr;
QJsonObject toJson() const;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -33,4 +33,21 @@ const QColor &MessageColor::getColor(Theme &themeManager) const
return _default; 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 } // namespace chatterino

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <QColor> #include <QColor>
#include <QString>
namespace chatterino { namespace chatterino {
class Theme; class Theme;
@ -13,6 +14,8 @@ struct MessageColor {
const QColor &getColor(Theme &themeManager) const; const QColor &getColor(Theme &themeManager) const;
QString toString() const;
private: private:
Type type_; Type type_;
QColor customColor_; QColor customColor_;

View file

@ -1,6 +1,7 @@
#include "messages/MessageElement.hpp" #include "messages/MessageElement.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "common/Literals.hpp"
#include "controllers/moderationactions/ModerationAction.hpp" #include "controllers/moderationactions/ModerationAction.hpp"
#include "debug/Benchmark.hpp" #include "debug/Benchmark.hpp"
#include "messages/Emote.hpp" #include "messages/Emote.hpp"
@ -14,8 +15,14 @@
#include "util/DebugCount.hpp" #include "util/DebugCount.hpp"
#include "util/Variant.hpp" #include "util/Variant.hpp"
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
namespace chatterino { namespace chatterino {
using namespace literals;
namespace { namespace {
// Computes the bounding box for the given vector of images // Computes the bounding box for the given vector of images
@ -88,6 +95,22 @@ void MessageElement::addFlags(MessageElementFlags flags)
this->flags_.set(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 // IMAGE
ImageElement::ImageElement(ImagePtr image, MessageElementFlags flags) ImageElement::ImageElement(ImagePtr image, MessageElementFlags flags)
: MessageElement(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, CircularImageElement::CircularImageElement(ImagePtr image, int padding,
QColor background, QColor background,
MessageElementFlags flags) 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 // EMOTE
EmoteElement::EmoteElement(const EmotePtr &emote, MessageElementFlags flags, EmoteElement::EmoteElement(const EmotePtr &emote, MessageElementFlags flags,
const MessageColor &textElementColor) const MessageColor &textElementColor)
@ -187,6 +230,19 @@ MessageLayoutElement *EmoteElement::makeImageLayoutElement(
return new ImageLayoutElement(*this, image, size); 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( LayeredEmoteElement::LayeredEmoteElement(
std::vector<LayeredEmoteElement::Emote> &&emotes, MessageElementFlags flags, std::vector<LayeredEmoteElement::Emote> &&emotes, MessageElementFlags flags,
const MessageColor &textElementColor) const MessageColor &textElementColor)
@ -350,6 +406,38 @@ std::vector<LayeredEmoteElement::Emote> LayeredEmoteElement::getUniqueEmotes()
return unique; 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 // BADGE
BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags) BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags)
: MessageElement(flags) : MessageElement(flags)
@ -390,6 +478,15 @@ MessageLayoutElement *BadgeElement::makeImageLayoutElement(
return element; 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 // MOD BADGE
ModBadgeElement::ModBadgeElement(const EmotePtr &data, ModBadgeElement::ModBadgeElement(const EmotePtr &data,
MessageElementFlags flags_) MessageElementFlags flags_)
@ -408,6 +505,14 @@ MessageLayoutElement *ModBadgeElement::makeImageLayoutElement(
return element; return element;
} }
QJsonObject ModBadgeElement::toJson() const
{
auto base = BadgeElement::toJson();
base["type"_L1] = u"ModBadgeElement"_s;
return base;
}
// VIP BADGE // VIP BADGE
VipBadgeElement::VipBadgeElement(const EmotePtr &data, VipBadgeElement::VipBadgeElement(const EmotePtr &data,
MessageElementFlags flags_) MessageElementFlags flags_)
@ -423,6 +528,14 @@ MessageLayoutElement *VipBadgeElement::makeImageLayoutElement(
return element; return element;
} }
QJsonObject VipBadgeElement::toJson() const
{
auto base = BadgeElement::toJson();
base["type"_L1] = u"VipBadgeElement"_s;
return base;
}
// FFZ Badge // FFZ Badge
FfzBadgeElement::FfzBadgeElement(const EmotePtr &data, FfzBadgeElement::FfzBadgeElement(const EmotePtr &data,
MessageElementFlags flags_, QColor color_) MessageElementFlags flags_, QColor color_)
@ -440,6 +553,15 @@ MessageLayoutElement *FfzBadgeElement::makeImageLayoutElement(
return element; 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 // TEXT
TextElement::TextElement(const QString &text, MessageElementFlags flags, TextElement::TextElement(const QString &text, MessageElementFlags flags,
const MessageColor &color, FontStyle style) 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, SingleLineTextElement::SingleLineTextElement(const QString &text,
MessageElementFlags flags, MessageElementFlags flags,
const MessageColor &color, 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, LinkElement::LinkElement(const Parsed &parsed, const QString &fullUrl,
MessageElementFlags flags, const MessageColor &color, MessageElementFlags flags, const MessageColor &color,
FontStyle style) FontStyle style)
@ -701,6 +850,17 @@ Link LinkElement::getLink() const
return {Link::Url, this->linkInfo_.url()}; 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_, MentionElement::MentionElement(const QString &displayName, QString loginName_,
MessageColor fallbackColor_, MessageColor fallbackColor_,
MessageColor userColor_) MessageColor userColor_)
@ -756,7 +916,24 @@ Link MentionElement::getLink() const
return {Link::UserInfo, this->userLoginName}; 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 // TIMESTAMP
TimestampElement::TimestampElement()
: TimestampElement(getApp()->isTest() ? QTime::fromMSecsSinceStartOfDay(0)
: QTime::currentTime())
{
}
TimestampElement::TimestampElement(QTime time) TimestampElement::TimestampElement(QTime time)
: MessageElement(MessageElementFlag::Timestamp) : MessageElement(MessageElementFlag::Timestamp)
, time_(time) , time_(time)
@ -790,6 +967,17 @@ TextElement *TimestampElement::formatTime(const QTime &time)
MessageColor::System, FontStyle::ChatMedium); 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 // TWITCH MODERATION
TwitchModerationElement::TwitchModerationElement() TwitchModerationElement::TwitchModerationElement()
: MessageElement(MessageElementFlag::ModeratorTools) : 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) LinebreakElement::LinebreakElement(MessageElementFlags flags)
: MessageElement(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, ScalingImageElement::ScalingImageElement(ImageSet images,
MessageElementFlags flags) MessageElementFlags flags)
: MessageElement(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() ReplyCurveElement::ReplyCurveElement()
: MessageElement(MessageElementFlag::RepliedMessage) : 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 } // namespace chatterino

View file

@ -7,6 +7,7 @@
#include "providers/links/LinkInfo.hpp" #include "providers/links/LinkInfo.hpp"
#include "singletons/Fonts.hpp" #include "singletons/Fonts.hpp"
#include <magic_enum/magic_enum.hpp>
#include <pajlada/signals/signalholder.hpp> #include <pajlada/signals/signalholder.hpp>
#include <QRect> #include <QRect>
#include <QString> #include <QString>
@ -16,6 +17,8 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
class QJsonObject;
namespace chatterino { namespace chatterino {
class Channel; class Channel;
struct MessageLayoutContainer; struct MessageLayoutContainer;
@ -183,6 +186,8 @@ public:
virtual void addToContainer(MessageLayoutContainer &container, virtual void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) = 0; MessageElementFlags flags) = 0;
virtual QJsonObject toJson() const;
protected: protected:
MessageElement(MessageElementFlags flags); MessageElement(MessageElementFlags flags);
bool trailingSpace = true; bool trailingSpace = true;
@ -202,6 +207,8 @@ public:
void addToContainer(MessageLayoutContainer &container, void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override; MessageElementFlags flags) override;
QJsonObject toJson() const override;
private: private:
ImagePtr image_; ImagePtr image_;
}; };
@ -216,6 +223,8 @@ public:
void addToContainer(MessageLayoutContainer &container, void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override; MessageElementFlags flags) override;
QJsonObject toJson() const override;
private: private:
ImagePtr image_; ImagePtr image_;
int padding_; int padding_;
@ -234,6 +243,8 @@ public:
void addToContainer(MessageLayoutContainer &container, void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override; MessageElementFlags flags) override;
QJsonObject toJson() const override;
protected: protected:
QStringList words_; QStringList words_;
@ -253,6 +264,8 @@ public:
void addToContainer(MessageLayoutContainer &container, void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override; MessageElementFlags flags) override;
QJsonObject toJson() const override;
private: private:
MessageColor color_; MessageColor color_;
FontStyle style_; FontStyle style_;
@ -294,6 +307,8 @@ public:
return &this->linkInfo_; return &this->linkInfo_;
} }
QJsonObject toJson() const override;
private: private:
LinkInfo linkInfo_; LinkInfo linkInfo_;
// these are implicitly shared // these are implicitly shared
@ -328,6 +343,8 @@ public:
MessageElement *setLink(const Link &link) override; MessageElement *setLink(const Link &link) override;
Link getLink() const override; Link getLink() const override;
QJsonObject toJson() const override;
private: private:
/** /**
* The color of the element in case the "Colorize @usernames" is disabled * The color of the element in case the "Colorize @usernames" is disabled
@ -355,6 +372,8 @@ public:
MessageElementFlags flags_) override; MessageElementFlags flags_) override;
EmotePtr getEmote() const; EmotePtr getEmote() const;
QJsonObject toJson() const override;
protected: protected:
virtual MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image, virtual MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
const QSize &size); const QSize &size);
@ -390,6 +409,8 @@ public:
std::vector<Emote> getUniqueEmotes() const; std::vector<Emote> getUniqueEmotes() const;
const std::vector<QString> &getEmoteTooltips() const; const std::vector<QString> &getEmoteTooltips() const;
QJsonObject toJson() const override;
private: private:
MessageLayoutElement *makeImageLayoutElement( MessageLayoutElement *makeImageLayoutElement(
const std::vector<ImagePtr> &image, const std::vector<QSize> &sizes, const std::vector<ImagePtr> &image, const std::vector<QSize> &sizes,
@ -416,6 +437,8 @@ public:
EmotePtr getEmote() const; EmotePtr getEmote() const;
QJsonObject toJson() const override;
protected: protected:
virtual MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image, virtual MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
const QSize &size); const QSize &size);
@ -429,6 +452,8 @@ class ModBadgeElement : public BadgeElement
public: public:
ModBadgeElement(const EmotePtr &data, MessageElementFlags flags_); ModBadgeElement(const EmotePtr &data, MessageElementFlags flags_);
QJsonObject toJson() const override;
protected: protected:
MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image, MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
const QSize &size) override; const QSize &size) override;
@ -439,6 +464,8 @@ class VipBadgeElement : public BadgeElement
public: public:
VipBadgeElement(const EmotePtr &data, MessageElementFlags flags_); VipBadgeElement(const EmotePtr &data, MessageElementFlags flags_);
QJsonObject toJson() const override;
protected: protected:
MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image, MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
const QSize &size) override; const QSize &size) override;
@ -450,6 +477,8 @@ public:
FfzBadgeElement(const EmotePtr &data, MessageElementFlags flags_, FfzBadgeElement(const EmotePtr &data, MessageElementFlags flags_,
QColor color_); QColor color_);
QJsonObject toJson() const override;
protected: protected:
MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image, MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
const QSize &size) override; const QSize &size) override;
@ -460,7 +489,8 @@ protected:
class TimestampElement : public MessageElement class TimestampElement : public MessageElement
{ {
public: public:
TimestampElement(QTime time_ = QTime::currentTime()); TimestampElement();
TimestampElement(QTime time_);
~TimestampElement() override = default; ~TimestampElement() override = default;
void addToContainer(MessageLayoutContainer &container, void addToContainer(MessageLayoutContainer &container,
@ -468,6 +498,8 @@ public:
TextElement *formatTime(const QTime &time); TextElement *formatTime(const QTime &time);
QJsonObject toJson() const override;
private: private:
QTime time_; QTime time_;
std::unique_ptr<TextElement> element_; std::unique_ptr<TextElement> element_;
@ -483,6 +515,8 @@ public:
void addToContainer(MessageLayoutContainer &container, void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override; MessageElementFlags flags) override;
QJsonObject toJson() const override;
}; };
// Forces a linebreak // Forces a linebreak
@ -493,6 +527,8 @@ public:
void addToContainer(MessageLayoutContainer &container, void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override; MessageElementFlags flags) override;
QJsonObject toJson() const override;
}; };
// Image element which will pick the quality of the image based on ui scale // Image element which will pick the quality of the image based on ui scale
@ -504,6 +540,8 @@ public:
void addToContainer(MessageLayoutContainer &container, void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override; MessageElementFlags flags) override;
QJsonObject toJson() const override;
private: private:
ImageSet images_; ImageSet images_;
}; };
@ -515,6 +553,13 @@ public:
void addToContainer(MessageLayoutContainer &container, void addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) override; MessageElementFlags flags) override;
QJsonObject toJson() const override;
}; };
} // namespace chatterino } // namespace chatterino
template <>
struct magic_enum::customize::enum_range<chatterino::MessageElementFlag> {
static constexpr bool is_flags = true; // NOLINT(readability-identifier-*)
};

View file

@ -1,12 +1,20 @@
#include "messages/MessageThread.hpp" #include "messages/MessageThread.hpp"
#include "common/Literals.hpp"
#include "messages/Message.hpp" #include "messages/Message.hpp"
#include "util/DebugCount.hpp" #include "util/DebugCount.hpp"
#include "util/QMagicEnum.hpp"
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <utility> #include <utility>
namespace chatterino { namespace chatterino {
using namespace literals;
MessageThread::MessageThread(std::shared_ptr<const Message> rootMessage) MessageThread::MessageThread(std::shared_ptr<const Message> rootMessage)
: rootMessageId_(rootMessage->id) : rootMessageId_(rootMessage->id)
, rootMessage_(std::move(rootMessage)) , rootMessage_(std::move(rootMessage))
@ -80,4 +88,29 @@ void MessageThread::markUnsubscribed()
this->subscriptionUpdated(); 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 } // namespace chatterino

View file

@ -6,6 +6,8 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
class QJsonObject;
namespace chatterino { namespace chatterino {
struct Message; struct Message;
@ -62,6 +64,8 @@ public:
return replies_; return replies_;
} }
QJsonObject toJson() const;
boost::signals2::signal<void()> subscriptionUpdated; boost::signals2::signal<void()> subscriptionUpdated;
private: private:

View file

@ -1,5 +1,6 @@
#include "providers/twitch/ChannelPointReward.hpp" #include "providers/twitch/ChannelPointReward.hpp"
#include "common/Literals.hpp"
#include "messages/Image.hpp" #include "messages/Image.hpp"
#include <QStringBuilder> #include <QStringBuilder>
@ -15,6 +16,8 @@ QString twitchChannelPointRewardUrl(const QString &file)
namespace chatterino { namespace chatterino {
using namespace literals;
ChannelPointReward::ChannelPointReward(const QJsonObject &redemption) ChannelPointReward::ChannelPointReward(const QJsonObject &redemption)
{ {
auto reward = redemption.value("reward").toObject(); 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 } // namespace chatterino

View file

@ -24,6 +24,8 @@ struct ChannelPointReward {
QString login; QString login;
QString displayName; QString displayName;
} user; } user;
QJsonObject toJson() const;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -256,40 +256,14 @@ void addHiddenContextMenuItems(QMenu *menu,
}); });
} }
const auto *message = layout->getMessage(); auto message = layout->getMessagePtr();
if (message != nullptr) if (message)
{ {
QJsonDocument jsonDocument; menu->addAction("Copy message &JSON", [message] {
auto jsonString = QJsonDocument{message->toJson()}.toJson(
QJsonObject jsonObject; QJsonDocument::Indented);
crossPlatformCopy(QString::fromUtf8(jsonString));
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);
}); });
} }
} }