From 4f79d6fc07978319143b7a34d03a8d19b398290e Mon Sep 17 00:00:00 2001 From: apa420 Date: Fri, 19 Apr 2019 22:44:02 +0200 Subject: [PATCH 1/4] Added deleted messages, will also add the disabled tag to denied automod messages --- src/Application.cpp | 1 + src/common/Channel.cpp | 25 +++++++++++++ src/common/Channel.hpp | 1 + .../moderationactions/ModerationAction.cpp | 4 +++ src/providers/twitch/IrcMessageHandler.cpp | 35 +++++++++++++++++++ src/providers/twitch/IrcMessageHandler.hpp | 1 + src/providers/twitch/PubsubClient.cpp | 3 ++ src/providers/twitch/TwitchMessageBuilder.cpp | 1 + src/providers/twitch/TwitchServer.cpp | 4 +++ src/widgets/helper/ChannelView.cpp | 4 ++- 10 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/Application.cpp b/src/Application.cpp index 2870c6ed6..d92e6e896 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -264,6 +264,7 @@ void Application::initPubsub() auto msg = MessageBuilder(action).release(); postToThread([chan, msg] { chan->addMessage(msg); }); + chan->deleteMessage(msg->id); }); this->twitch.pubsub->start(); diff --git a/src/common/Channel.cpp b/src/common/Channel.cpp index 905bd1b6c..82432db44 100644 --- a/src/common/Channel.cpp +++ b/src/common/Channel.cpp @@ -212,6 +212,31 @@ void Channel::replaceMessage(MessagePtr message, MessagePtr replacement) } } +void Channel::deleteMessage(QString messageID) +{ + LimitedQueueSnapshot snapshot = this->getMessageSnapshot(); + int snapshotLength = snapshot.size(); + + int end = std::max(0, snapshotLength - 20); + + QTime minimumTime = QTime::currentTime().addSecs(-5); + for (int i = snapshotLength - 1; i >= end; --i) + { + auto &s = snapshot[i]; + + if (s->parseTime < minimumTime) + { + break; + } + + if (s->id == messageID) + { + s->flags.set(MessageFlag::Disabled); + break; + } + } +} + void Channel::addRecentChatter(const MessagePtr &message) { } diff --git a/src/common/Channel.hpp b/src/common/Channel.hpp index c45d9c973..9abc668ea 100644 --- a/src/common/Channel.hpp +++ b/src/common/Channel.hpp @@ -62,6 +62,7 @@ public: void addOrReplaceTimeout(MessagePtr message); void disableAllMessages(); void replaceMessage(MessagePtr message, MessagePtr replacement); + void deleteMessage(QString messageID); QStringList modList; diff --git a/src/controllers/moderationactions/ModerationAction.cpp b/src/controllers/moderationactions/ModerationAction.cpp index 0b6418a57..8683f2c4e 100644 --- a/src/controllers/moderationactions/ModerationAction.cpp +++ b/src/controllers/moderationactions/ModerationAction.cpp @@ -73,6 +73,10 @@ ModerationAction::ModerationAction(const QString &action) { this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban); } + else if (action.startsWith("/delete")) + { + this->image_ = Image::fromPixmap(getApp()->resources->pajaDank); + } else { QString xD = action; diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index 6c9d9b592..26766b1c0 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -267,6 +267,41 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message) } } +void IrcMessageHandler::handleClearMessageMessage(Communi::IrcMessage *message) +{ + // check parameter count + if (message->parameters().length() < 1) + { + return; + } + + QString chanName; + if (!trimChannelName(message->parameter(0), chanName)) + { + return; + } + + auto app = getApp(); + + // get channel + auto chan = app->twitch.server->getChannelOrEmpty(chanName); + + if (chan->isEmpty()) + { + log("[IrcMessageHandler:handleClearMessageMessage] Twitch channel {} " + "not " + "found", + chanName); + return; + } + + auto tags = message->tags(); + + QString targetID = tags.value("target-msg-id").toString(); + + chan->deleteMessage(targetID); +} + void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message) { auto app = getApp(); diff --git a/src/providers/twitch/IrcMessageHandler.hpp b/src/providers/twitch/IrcMessageHandler.hpp index 0474f097b..861ee734e 100644 --- a/src/providers/twitch/IrcMessageHandler.hpp +++ b/src/providers/twitch/IrcMessageHandler.hpp @@ -27,6 +27,7 @@ public: void handleRoomStateMessage(Communi::IrcMessage *message); void handleClearChatMessage(Communi::IrcMessage *message); + void handleClearMessageMessage(Communi::IrcMessage *message); void handleUserStateMessage(Communi::IrcMessage *message); void handleWhisperMessage(Communi::IrcMessage *message); diff --git a/src/providers/twitch/PubsubClient.cpp b/src/providers/twitch/PubsubClient.cpp index 0a8976618..5108c5ad4 100644 --- a/src/providers/twitch/PubsubClient.cpp +++ b/src/providers/twitch/PubsubClient.cpp @@ -710,6 +710,9 @@ PubSub::PubSub() // qDebug() << QString::fromStdString(rj::stringify(data)); }; + this->moderationActionHandlers["delete"] = + [this](const auto &data, const auto &roomID) { qDebug() << "xd"; }; + this->websocketClient.set_access_channels(websocketpp::log::alevel::all); this->websocketClient.clear_access_channels( websocketpp::log::alevel::frame_payload); diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index 204cf6c04..6073e6449 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -582,6 +582,7 @@ void TwitchMessageBuilder::parseMessageID() { this->messageID = iterator.value().toString(); } + this->message().id = this->messageID; } void TwitchMessageBuilder::parseRoomID() diff --git a/src/providers/twitch/TwitchServer.cpp b/src/providers/twitch/TwitchServer.cpp index 349e40357..eaf90026e 100644 --- a/src/providers/twitch/TwitchServer.cpp +++ b/src/providers/twitch/TwitchServer.cpp @@ -156,6 +156,10 @@ void TwitchServer::messageReceived(Communi::IrcMessage *message) { handler.handleClearChatMessage(message); } + else if (command == "CLEARMSG") + { + handler.handleClearMessageMessage(message); + } else if (command == "USERSTATE") { handler.handleUserStateMessage(message); diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 9578c352a..a1fdf645f 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -1667,7 +1667,9 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link, case Link::UserAction: { QString value = link.value; - value.replace("{user}", layout->getMessage()->loginName).replace("{channel}", this->channel_->getName()); + value.replace("{user}", layout->getMessage()->loginName) + .replace("{channel}", this->channel_->getName()) + .replace("{msg-id}", layout->getMessage()->id); this->channel_->sendMessage(value); } break; From 3c1d2646898116cceefb7d66fb44499a10ca570b Mon Sep 17 00:00:00 2001 From: apa420 Date: Sun, 28 Apr 2019 02:25:05 +0200 Subject: [PATCH 2/4] Added trashcan, replaced messageID var with this-message().id, removed pubsub stuff that didn't belong --- resources/buttons/trashCan.png | Bin 0 -> 3087 bytes resources/resources_autogenerated.qrc | 1 + src/autogenerated/ResourcesAutogen.cpp | 1 + src/autogenerated/ResourcesAutogen.hpp | 1 + src/common/Channel.cpp | 8 +------- .../moderationactions/ModerationAction.cpp | 2 +- src/providers/twitch/PubsubClient.cpp | 3 --- src/providers/twitch/TwitchMessageBuilder.cpp | 5 ++--- src/providers/twitch/TwitchMessageBuilder.hpp | 7 ++++--- src/widgets/settingspages/ModerationPage.cpp | 3 ++- 10 files changed, 13 insertions(+), 18 deletions(-) create mode 100644 resources/buttons/trashCan.png diff --git a/resources/buttons/trashCan.png b/resources/buttons/trashCan.png new file mode 100644 index 0000000000000000000000000000000000000000..b5e11bf4969ebe501019b7be6bb691bc5d967cd4 GIT binary patch literal 3087 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 AivR!s literal 0 HcmV?d00001 diff --git a/resources/resources_autogenerated.qrc b/resources/resources_autogenerated.qrc index fcf0b5dac..4c15a6a82 100644 --- a/resources/resources_autogenerated.qrc +++ b/resources/resources_autogenerated.qrc @@ -24,6 +24,7 @@ buttons/modModeEnabled.png buttons/modModeEnabled2.png buttons/timeout.png + buttons/trashCan.png buttons/unban.png buttons/unmod.png buttons/update.png diff --git a/src/autogenerated/ResourcesAutogen.cpp b/src/autogenerated/ResourcesAutogen.cpp index 34cf039ef..59f5fce70 100644 --- a/src/autogenerated/ResourcesAutogen.cpp +++ b/src/autogenerated/ResourcesAutogen.cpp @@ -18,6 +18,7 @@ Resources2::Resources2() this->buttons.modModeEnabled = QPixmap(":/buttons/modModeEnabled.png"); this->buttons.modModeEnabled2 = QPixmap(":/buttons/modModeEnabled2.png"); this->buttons.timeout = QPixmap(":/buttons/timeout.png"); + this->buttons.trashCan = QPixmap(":/buttons/trashCan.png"); this->buttons.unban = QPixmap(":/buttons/unban.png"); this->buttons.unmod = QPixmap(":/buttons/unmod.png"); this->buttons.update = QPixmap(":/buttons/update.png"); diff --git a/src/autogenerated/ResourcesAutogen.hpp b/src/autogenerated/ResourcesAutogen.hpp index fbbb2fbf3..1c8a0bce4 100644 --- a/src/autogenerated/ResourcesAutogen.hpp +++ b/src/autogenerated/ResourcesAutogen.hpp @@ -24,6 +24,7 @@ public: QPixmap modModeEnabled; QPixmap modModeEnabled2; QPixmap timeout; + QPixmap trashCan; QPixmap unban; QPixmap unmod; QPixmap update; diff --git a/src/common/Channel.cpp b/src/common/Channel.cpp index 82432db44..caa432752 100644 --- a/src/common/Channel.cpp +++ b/src/common/Channel.cpp @@ -217,18 +217,12 @@ void Channel::deleteMessage(QString messageID) LimitedQueueSnapshot snapshot = this->getMessageSnapshot(); int snapshotLength = snapshot.size(); - int end = std::max(0, snapshotLength - 20); + int end = std::max(0, snapshotLength - 200); - QTime minimumTime = QTime::currentTime().addSecs(-5); for (int i = snapshotLength - 1; i >= end; --i) { auto &s = snapshot[i]; - if (s->parseTime < minimumTime) - { - break; - } - if (s->id == messageID) { s->flags.set(MessageFlag::Disabled); diff --git a/src/controllers/moderationactions/ModerationAction.cpp b/src/controllers/moderationactions/ModerationAction.cpp index 8683f2c4e..199ea5521 100644 --- a/src/controllers/moderationactions/ModerationAction.cpp +++ b/src/controllers/moderationactions/ModerationAction.cpp @@ -75,7 +75,7 @@ ModerationAction::ModerationAction(const QString &action) } else if (action.startsWith("/delete")) { - this->image_ = Image::fromPixmap(getApp()->resources->pajaDank); + this->image_ = Image::fromPixmap(getApp()->resources->buttons.trashCan); } else { diff --git a/src/providers/twitch/PubsubClient.cpp b/src/providers/twitch/PubsubClient.cpp index 5108c5ad4..0a8976618 100644 --- a/src/providers/twitch/PubsubClient.cpp +++ b/src/providers/twitch/PubsubClient.cpp @@ -710,9 +710,6 @@ PubSub::PubSub() // qDebug() << QString::fromStdString(rj::stringify(data)); }; - this->moderationActionHandlers["delete"] = - [this](const auto &data, const auto &roomID) { qDebug() << "xd"; }; - this->websocketClient.set_access_channels(websocketpp::log::alevel::all); this->websocketClient.clear_access_channels( websocketpp::log::alevel::frame_payload); diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index 6073e6449..b935a2831 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -580,9 +580,8 @@ void TwitchMessageBuilder::parseMessageID() if (iterator != this->tags.end()) { - this->messageID = iterator.value().toString(); + this->message().id = iterator.value().toString(); } - this->message().id = this->messageID; } void TwitchMessageBuilder::parseRoomID() @@ -608,7 +607,7 @@ void TwitchMessageBuilder::parseRoomID() void TwitchMessageBuilder::appendChannelName() { QString channelName("#" + this->channel->getName()); - Link link(Link::Url, this->channel->getName() + "\n" + this->messageID); + Link link(Link::Url, this->channel->getName() + "\n" + this->message().id); this->emplace(channelName, MessageElementFlag::ChannelName, MessageColor::System) // diff --git a/src/providers/twitch/TwitchMessageBuilder.hpp b/src/providers/twitch/TwitchMessageBuilder.hpp index 5aa72b204..da7b3865c 100644 --- a/src/providers/twitch/TwitchMessageBuilder.hpp +++ b/src/providers/twitch/TwitchMessageBuilder.hpp @@ -41,7 +41,6 @@ public: MessageParseArgs args; const QVariantMap tags; - QString messageID; QString userName; bool isIgnored() const; @@ -55,8 +54,10 @@ private: void appendUsername(); void parseHighlights(bool isPastMsg); - void appendTwitchEmote(const QString &emote, - std::vector> &vec, std::vector &correctPositions); + void appendTwitchEmote( + const QString &emote, + std::vector> &vec, + std::vector &correctPositions); Outcome tryAppendEmote(const EmoteName &name); void addWords( diff --git a/src/widgets/settingspages/ModerationPage.cpp b/src/widgets/settingspages/ModerationPage.cpp index 3349d2e93..b44e73274 100644 --- a/src/widgets/settingspages/ModerationPage.cpp +++ b/src/widgets/settingspages/ModerationPage.cpp @@ -161,7 +161,8 @@ ModerationPage::ModerationPage() // clang-format off auto label = modMode.emplace( "Moderation mode is enabled by clicking in a channel that you moderate.

" - "Moderation buttons can be bound to chat commands such as \"/ban {user}\", \"/timeout {user} 1000\", \"/w someusername !report {user} was bad in channel {channel}\" or any other custom text commands.
"); + "Moderation buttons can be bound to chat commands such as \"/ban {user}\", \"/timeout {user} 1000\", \"/w someusername !report {user} was bad in channel {channel}\" or any other custom text commands.
" + "For deleting messages use /delete {msg-id}."); label->setWordWrap(true); label->setStyleSheet("color: #bbb"); // clang-format on From ae0122e38976ec215b397daac993004c129eed47 Mon Sep 17 00:00:00 2001 From: apa420 Date: Sun, 28 Apr 2019 11:31:34 +0200 Subject: [PATCH 3/4] Added a message when an invalid /delete appears --- src/providers/twitch/IrcMessageHandler.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index 26766b1c0..e1c4c7216 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -552,7 +552,17 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message) return; } - channel->addMessage(msg); + QString tags = message->tags().value("msg-id", "").toString(); + if (tags == "bad_delete_message_error" || tags == "usage_delete") + { + channel->addMessage(makeSystemMessage( + "Usage: \"/delete \" - can't take more " + "than one argument")); + } + else + { + channel->addMessage(msg); + } } } From 31ab2428f6c22c00c37236920fcb380e056289f6 Mon Sep 17 00:00:00 2001 From: apa420 Date: Sun, 28 Apr 2019 12:46:55 +0200 Subject: [PATCH 4/4] Pajlada made a nice trashcan, thank him :) --- resources/buttons/trashCan.png | Bin 3087 -> 687 bytes resources/buttons/trashcan.svg | 124 +++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 resources/buttons/trashcan.svg diff --git a/resources/buttons/trashCan.png b/resources/buttons/trashCan.png index b5e11bf4969ebe501019b7be6bb691bc5d967cd4..fd1bce3a0bb38891e1bcc82969b23ca38f673928 100644 GIT binary patch literal 687 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=EX7WqAsj$Z!;#Vf4nJ zuwMXS#+}{I=K}>LOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zA((!b0 z45^s&cJ{_RVMl?s|CT`)KZuCDRM_j7nbFXf&TulK&a@9Yn#|DfD)MLDmzfV0 zdzM{YV#)U8>!pX8l|McmxOLQ~p~`Ciaz=Zlz0QFr{_fp=c>XlI)>LB#hY5_24vI^C zxDb{8Ryd&FpG~WRi?dv4`yTG2d0&Jt>T~_^yXDtizv#!7|9L-jZyA?um^J50!#h>c zh02>h73|nwRPyt{Q}gxlxl3{cr?Q{+cS_;ApdRM*ZOy&ee^quQx-d;JuIAMad!MV? zaJt+j&CF@q37u_1B@t|?3`fiMu-7q4#pYeQxT$`6>Ibb=i=T;Toqx>qYom?C-i&JB zwEFohH<@y57`8D;PiA_S~4SwI}2pZ0ChbJ&9?abNzAU)Lr}ZZ!!lQ)G+f4 zy)WJ!{@~lJ!d1WGeyE%fOL)wv97A+NFZ%&vg^~pwB8-9Nb^o#NW4fRG zIOJ>X%~N+B#JbaqvS0HuBz*jDQCb_ni9MjM;w$?BgYeq;8le>K4Ld(FE}6?5!ge*V zc-EI$6Xi7Zr&kHxY4iG1zeoC1Z#}C`!!~t~3HvQwf-W3gdsukGNo%iLySMTs6qipq tQr;3%9d^C!r&-vxC$nS~`-)A)Eq-rTJnh&y9hkTnJYD@<);T3K0RTr?B&YxY literal 3087 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 AivR!s diff --git a/resources/buttons/trashcan.svg b/resources/buttons/trashcan.svg new file mode 100644 index 000000000..e225ee4ea --- /dev/null +++ b/resources/buttons/trashcan.svg @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + Trashcan top + + + + + + +