mirror-chatterino2/src/messages/MessageBuilder.cpp

443 lines
14 KiB
C++
Raw Normal View History

2018-06-26 14:09:39 +02:00
#include "MessageBuilder.hpp"
#include "Application.hpp"
#include "common/LinkParser.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "messages/Image.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
2019-03-13 15:26:55 +01:00
#include "providers/LinkResolver.hpp"
#include "providers/twitch/PubsubActions.hpp"
2018-06-28 19:46:45 +02:00
#include "singletons/Emotes.hpp"
#include "singletons/Resources.hpp"
2018-06-28 20:03:04 +02:00
#include "singletons/Theme.hpp"
2018-08-07 01:35:24 +02:00
#include "util/FormatTime.hpp"
#include "util/IrcHelpers.hpp"
2017-04-12 17:46:44 +02:00
#include <QDateTime>
#include <QImageReader>
2017-04-14 17:52:22 +02:00
namespace chatterino {
2017-04-12 17:46:44 +02:00
2018-08-07 01:35:24 +02:00
MessagePtr makeSystemMessage(const QString &text)
{
return MessageBuilder(systemMessage, text).release();
}
std::pair<MessagePtr, MessagePtr> makeAutomodMessage(
const AutomodAction &action)
{
auto builder = MessageBuilder();
builder.emplace<TimestampElement>();
builder.message().flags.set(MessageFlag::PubSub);
builder
2019-09-26 00:51:05 +02:00
.emplace<ImageElement>(Image::fromPixmap(getResources().twitch.automod),
MessageElementFlag::BadgeChannelAuthority)
->setTooltip("AutoMod");
builder.emplace<TextElement>("AutoMod:", MessageElementFlag::BoldUsername,
MessageColor(QColor("blue")),
FontStyle::ChatMediumBold);
builder.emplace<TextElement>(
"AutoMod:", MessageElementFlag::NonBoldUsername,
MessageColor(QColor("blue")));
builder.emplace<TextElement>(
("Held a message for reason: " + action.reason +
". Allow will post it in chat. "),
MessageElementFlag::Text, MessageColor::Text);
builder
.emplace<TextElement>("Allow", MessageElementFlag::Text,
MessageColor(QColor("green")),
FontStyle::ChatMediumBold)
->setLink({Link::AutoModAllow, action.msgID});
builder
.emplace<TextElement>(" Deny", MessageElementFlag::Text,
MessageColor(QColor("red")),
FontStyle::ChatMediumBold)
->setLink({Link::AutoModDeny, action.msgID});
// builder.emplace<TextElement>(action.msgID,
// MessageElementFlag::Text,
// MessageColor::Text);
builder.message().flags.set(MessageFlag::AutoMod);
auto message1 = builder.release();
builder = MessageBuilder();
builder.emplace<TimestampElement>();
2019-05-18 17:37:26 +02:00
builder.emplace<TwitchModerationElement>();
builder.message().loginName = action.target.name;
builder.message().flags.set(MessageFlag::PubSub);
builder
.emplace<TextElement>(
action.target.name + ":", MessageElementFlag::BoldUsername,
MessageColor(QColor("red")), FontStyle::ChatMediumBold)
->setLink({Link::UserInfo, action.target.name});
builder
.emplace<TextElement>(action.target.name + ":",
MessageElementFlag::NonBoldUsername,
MessageColor(QColor("red")))
->setLink({Link::UserInfo, action.target.name});
builder.emplace<TextElement>(action.message, MessageElementFlag::Text,
MessageColor::Text);
builder.message().flags.set(MessageFlag::AutoMod);
auto message2 = builder.release();
return std::make_pair(message1, message2);
}
2017-04-12 17:46:44 +02:00
MessageBuilder::MessageBuilder()
: message_(std::make_shared<Message>())
2018-08-07 01:35:24 +02:00
{
}
MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text)
: MessageBuilder()
2017-04-12 17:46:44 +02:00
{
2018-08-07 01:35:24 +02:00
this->emplace<TimestampElement>();
// check system message for links
// (e.g. needed for sub ticket message in sub only mode)
2019-05-03 08:58:54 +02:00
const QStringList textFragments = text.split(QRegularExpression("\\s"));
for (const auto &word : textFragments)
{
2019-05-03 08:58:54 +02:00
const auto linkString = this->matchLink(word);
if (linkString.isEmpty())
{
this->emplace<TextElement>(word, MessageElementFlag::Text,
MessageColor::System);
}
else
{
this->addLink(word, linkString);
}
}
2018-08-07 07:55:31 +02:00
this->message().flags.set(MessageFlag::System);
this->message().flags.set(MessageFlag::DoNotTriggerNotification);
this->message().messageText = text;
2018-08-07 01:35:24 +02:00
this->message().searchText = text;
2017-04-12 17:46:44 +02:00
}
2018-08-07 01:35:24 +02:00
MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
const QString &durationInSeconds,
const QString &reason, bool multipleTimes)
: MessageBuilder()
2017-04-12 17:46:44 +02:00
{
2018-08-07 01:35:24 +02:00
QString text;
text.append(username);
2018-10-21 13:43:02 +02:00
if (!durationInSeconds.isEmpty())
{
2018-08-07 01:35:24 +02:00
text.append(" has been timed out");
// TODO: Implement who timed the user out
text.append(" for ");
bool ok = true;
int timeoutSeconds = durationInSeconds.toInt(&ok);
2018-10-21 13:43:02 +02:00
if (ok)
{
2018-08-07 01:35:24 +02:00
text.append(formatTime(timeoutSeconds));
}
2018-10-21 13:43:02 +02:00
}
else
{
2018-08-07 01:35:24 +02:00
text.append(" has been permanently banned");
}
2018-10-21 13:43:02 +02:00
if (reason.length() > 0)
{
2018-08-07 01:35:24 +02:00
text.append(": \"");
text.append(parseTagString(reason));
text.append("\"");
}
text.append(".");
2018-10-21 13:43:02 +02:00
if (multipleTimes)
{
2018-08-07 01:35:24 +02:00
text.append(" (multiple times)");
}
2018-08-07 07:55:31 +02:00
this->message().flags.set(MessageFlag::System);
this->message().flags.set(MessageFlag::Timeout);
this->message().flags.set(MessageFlag::DoNotTriggerNotification);
2018-08-07 01:35:24 +02:00
this->message().timeoutUser = username;
this->emplace<TimestampElement>();
this->emplace<TextElement>(text, MessageElementFlag::Text,
MessageColor::System);
this->message().messageText = text;
this->message().searchText = text;
2017-04-12 17:46:44 +02:00
}
// XXX: This does not belong in the MessageBuilder, this should be part of the TwitchMessageBuilder
2018-08-07 01:35:24 +02:00
MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
2018-08-08 15:35:54 +02:00
: MessageBuilder()
2017-04-12 17:46:44 +02:00
{
auto current = getApp()->accounts->twitch.getCurrent();
2018-08-07 01:35:24 +02:00
this->emplace<TimestampElement>();
2018-08-07 07:55:31 +02:00
this->message().flags.set(MessageFlag::System);
this->message().flags.set(MessageFlag::Timeout);
2018-08-07 01:35:24 +02:00
this->message().timeoutUser = action.target.name;
this->message().count = count;
QString text;
if (action.target.id == current->getUserId())
2018-10-21 13:43:02 +02:00
{
text.append("You were ");
if (action.isBan())
{
text.append("banned");
}
else
{
text.append(
QString("timed out for %1").arg(formatTime(action.duration)));
}
if (!action.source.name.isEmpty())
{
text.append(" by ");
text.append(action.source.name);
}
2018-10-21 13:43:02 +02:00
if (action.reason.isEmpty())
{
text.append(".");
2018-10-21 13:43:02 +02:00
}
else
{
text.append(QString(": \"%1\".").arg(action.reason));
2018-08-07 01:35:24 +02:00
}
2018-10-21 13:43:02 +02:00
}
else
{
if (action.isBan())
2018-10-21 13:43:02 +02:00
{
if (action.reason.isEmpty())
{
text = QString("%1 banned %2.") //
.arg(action.source.name)
.arg(action.target.name);
}
else
{
text = QString("%1 banned %2: \"%3\".") //
.arg(action.source.name)
.arg(action.target.name)
.arg(action.reason);
}
2018-10-21 13:43:02 +02:00
}
else
{
if (action.reason.isEmpty())
{
text = QString("%1 timed out %2 for %3.") //
.arg(action.source.name)
.arg(action.target.name)
.arg(formatTime(action.duration));
}
else
{
text = QString("%1 timed out %2 for %3: \"%4\".") //
.arg(action.source.name)
.arg(action.target.name)
.arg(formatTime(action.duration))
.arg(action.reason);
}
2018-08-07 01:35:24 +02:00
if (count > 1)
{
text.append(QString(" (%1 times)").arg(count));
}
2018-08-07 01:35:24 +02:00
}
}
2018-08-07 07:55:31 +02:00
this->emplace<TextElement>(text, MessageElementFlag::Text,
2018-08-07 01:35:24 +02:00
MessageColor::System);
this->message().messageText = text;
2018-08-07 01:35:24 +02:00
this->message().searchText = text;
2017-04-12 17:46:44 +02:00
}
2018-08-07 01:35:24 +02:00
MessageBuilder::MessageBuilder(const UnbanAction &action)
2018-08-08 15:35:54 +02:00
: MessageBuilder()
2017-07-31 00:57:42 +02:00
{
2018-08-07 01:35:24 +02:00
this->emplace<TimestampElement>();
2018-08-07 07:55:31 +02:00
this->message().flags.set(MessageFlag::System);
this->message().flags.set(MessageFlag::Untimeout);
2018-08-07 01:35:24 +02:00
this->message().timeoutUser = action.target.name;
QString text =
QString("%1 %2 %3.")
.arg(action.source.name)
.arg(QString(action.wasBan() ? "unbanned" : "untimedout"))
.arg(action.target.name);
2018-08-07 01:35:24 +02:00
2018-08-07 07:55:31 +02:00
this->emplace<TextElement>(text, MessageElementFlag::Text,
2018-08-07 01:35:24 +02:00
MessageColor::System);
this->message().messageText = text;
2018-08-07 01:35:24 +02:00
this->message().searchText = text;
}
MessageBuilder::MessageBuilder(const AutomodUserAction &action)
: MessageBuilder()
{
this->emplace<TimestampElement>();
this->message().flags.set(MessageFlag::System);
QString text;
2019-01-22 23:20:43 +01:00
switch (action.type)
{
2019-09-26 00:51:05 +02:00
case AutomodUserAction::AddPermitted: {
2019-01-22 23:20:43 +01:00
text = QString("%1 added %2 as a permitted term on AutoMod.")
.arg(action.source.name)
.arg(action.message);
}
break;
2019-09-26 00:51:05 +02:00
case AutomodUserAction::AddBlocked: {
2019-01-22 23:20:43 +01:00
text = QString("%1 added %2 as a blocked term on AutoMod.")
.arg(action.source.name)
.arg(action.message);
}
break;
2019-09-26 00:51:05 +02:00
case AutomodUserAction::RemovePermitted: {
2019-01-22 23:20:43 +01:00
text = QString("%1 removed %2 as a permitted term term on AutoMod.")
.arg(action.source.name)
.arg(action.message);
}
break;
2019-09-26 00:51:05 +02:00
case AutomodUserAction::RemoveBlocked: {
2019-01-22 23:20:43 +01:00
text = QString("%1 removed %2 as a blocked term on AutoMod.")
.arg(action.source.name)
.arg(action.message);
}
break;
2019-09-26 00:51:05 +02:00
case AutomodUserAction::Properties: {
2019-01-22 23:20:43 +01:00
text = QString("%1 modified the AutoMod properties.")
.arg(action.source.name);
}
break;
}
this->emplace<TextElement>(text, MessageElementFlag::Text,
MessageColor::System);
}
2018-08-07 01:35:24 +02:00
Message *MessageBuilder::operator->()
{
return this->message_.get();
}
Message &MessageBuilder::message()
{
return *this->message_;
}
MessagePtr MessageBuilder::release()
{
std::shared_ptr<Message> ptr;
this->message_.swap(ptr);
return ptr;
}
std::weak_ptr<Message> MessageBuilder::weakOf()
{
return this->message_;
}
2018-08-07 01:35:24 +02:00
void MessageBuilder::append(std::unique_ptr<MessageElement> element)
2017-04-12 17:46:44 +02:00
{
2018-08-07 01:35:24 +02:00
this->message().elements.push_back(std::move(element));
2017-04-12 17:46:44 +02:00
}
QString MessageBuilder::matchLink(const QString &string)
{
LinkParser linkParser(string);
2018-08-06 21:17:03 +02:00
static QRegularExpression httpRegex(
"\\bhttps?://", QRegularExpression::CaseInsensitiveOption);
static QRegularExpression ftpRegex(
"\\bftps?://", QRegularExpression::CaseInsensitiveOption);
static QRegularExpression spotifyRegex(
"\\bspotify:", QRegularExpression::CaseInsensitiveOption);
2017-08-12 12:09:26 +02:00
2018-10-21 13:43:02 +02:00
if (!linkParser.hasMatch())
{
2017-08-12 12:09:26 +02:00
return QString();
}
2017-08-05 18:44:14 +02:00
QString captured = linkParser.getCaptured();
2018-08-06 21:17:03 +02:00
if (!captured.contains(httpRegex) && !captured.contains(ftpRegex) &&
2018-10-21 13:43:02 +02:00
!captured.contains(spotifyRegex))
{
captured.insert(0, "http://");
}
2017-09-24 18:43:24 +02:00
return captured;
2017-04-12 17:46:44 +02:00
}
2019-03-13 15:26:55 +01:00
void MessageBuilder::addLink(const QString &origLink,
const QString &matchedLink)
{
static QRegularExpression domainRegex(
R"(^(?:(?:ftp|http)s?:\/\/)?([^\/]+)(?:\/.*)?$)",
QRegularExpression::CaseInsensitiveOption);
QString lowercaseLinkString;
auto match = domainRegex.match(origLink);
if (match.isValid())
{
lowercaseLinkString = origLink.mid(0, match.capturedStart(1)) +
match.captured(1).toLower() +
origLink.mid(match.capturedEnd(1));
}
else
{
lowercaseLinkString = origLink;
}
auto linkElement = Link(Link::Url, matchedLink);
auto textColor = MessageColor(MessageColor::Link);
auto linkMELowercase =
this->emplace<TextElement>(lowercaseLinkString,
MessageElementFlag::LowercaseLink, textColor)
->setLink(linkElement);
auto linkMEOriginal =
this->emplace<TextElement>(origLink, MessageElementFlag::OriginalLink,
textColor)
->setLink(linkElement);
2019-08-20 23:30:39 +02:00
LinkResolver::getLinkInfo(
matchedLink, nullptr,
[weakMessage = this->weakOf(), linkMELowercase, linkMEOriginal,
matchedLink](QString tooltipText, Link originalLink) {
auto shared = weakMessage.lock();
if (!shared)
{
return;
}
if (!tooltipText.isEmpty())
{
linkMELowercase->setTooltip(tooltipText);
linkMEOriginal->setTooltip(tooltipText);
}
if (originalLink.value != matchedLink &&
!originalLink.value.isEmpty())
{
linkMELowercase->setLink(originalLink)->updateLink();
linkMEOriginal->setLink(originalLink)->updateLink();
}
});
2019-03-13 15:26:55 +01:00
}
} // namespace chatterino