diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index c5526d44a..be01f5b0e 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -44,6 +44,46 @@ IrcMessageHandler &IrcMessageHandler::getInstance() return instance; } +std::vector IrcMessageHandler::parseMessage( + Channel *channel, Communi::IrcMessage *message) +{ + std::vector builtMessages; + + auto command = message->command(); + + if (command == "PRIVMSG") + { + return this->parsePrivMessage( + channel, static_cast(message)); + } + else if (command == "USERNOTICE") + { + return this->parseUserNoticeMessage(channel, message); + } + else if (command == "NOTICE") + { + return this->parseNoticeMessage( + static_cast(message)); + } + + return builtMessages; +} + +std::vector IrcMessageHandler::parsePrivMessage( + Channel *channel, Communi::IrcPrivateMessage *message) +{ + log("Parse priv msg"); + std::vector builtMessages; + MessageParseArgs args; + TwitchMessageBuilder builder(channel, message, args, message->content(), + message->isAction()); + if (!builder.isIgnored()) + { + builtMessages.emplace_back(builder.build()); + } + return builtMessages; +} + void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server) { @@ -302,6 +342,56 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message) } } +std::vector IrcMessageHandler::parseUserNoticeMessage( + Channel *channel, Communi::IrcMessage *message) +{ + std::vector builtMessages; + + auto data = message->toData(); + + auto tags = message->tags(); + auto parameters = message->parameters(); + + auto target = parameters[0]; + QString msgType = tags.value("msg-id", "").toString(); + QString content; + if (parameters.size() >= 2) + { + content = parameters[1]; + } + + if (msgType == "sub" || msgType == "resub" || msgType == "subgift") + { + // Sub-specific message. I think it's only allowed for "resub" messages + // atm + if (!content.isEmpty()) + { + MessageParseArgs args; + args.trimSubscriberUsername = true; + + TwitchMessageBuilder builder(channel, message, args, content, + false); + builder->flags.set(MessageFlag::Subscription); + builder->flags.unset(MessageFlag::Highlighted); + builtMessages.emplace_back(builder.build()); + } + } + + auto it = tags.find("system-msg"); + + if (it != tags.end()) + { + auto b = MessageBuilder(systemMessage, + parseTagString(it.value().toString())); + + b->flags.set(MessageFlag::Subscription); + auto newMessage = b.release(); + builtMessages.emplace_back(newMessage); + } + + return builtMessages; +} + void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, TwitchServer &server) { @@ -381,35 +471,49 @@ void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message) } } +std::vector IrcMessageHandler::parseNoticeMessage( + Communi::IrcNoticeMessage *message) +{ + std::vector builtMessages; + + builtMessages.emplace_back(makeSystemMessage(message->content())); + + return builtMessages; +} + void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message) { auto app = getApp(); - MessagePtr msg = makeSystemMessage(message->content()); + auto builtMessages = this->parseNoticeMessage(message); - QString channelName; - if (!trimChannelName(message->target(), channelName)) + for (auto msg : builtMessages) { - // Notice wasn't targeted at a single channel, send to all twitch - // channels - app->twitch.server->forEachChannelAndSpecialChannels( - [msg](const auto &c) { - c->addMessage(msg); // - }); + QString channelName; + if (!trimChannelName(message->target(), channelName)) + { + // Notice wasn't targeted at a single channel, send to all twitch + // channels + app->twitch.server->forEachChannelAndSpecialChannels( + [msg](const auto &c) { + c->addMessage(msg); // + }); - return; + return; + } + + auto channel = app->twitch.server->getChannelOrEmpty(channelName); + + if (channel->isEmpty()) + { + log("[IrcManager:handleNoticeMessage] Channel {} not found in " + "channel " + "manager ", + channelName); + return; + } + + channel->addMessage(msg); } - - auto channel = app->twitch.server->getChannelOrEmpty(channelName); - - if (channel->isEmpty()) - { - log("[IrcManager:handleNoticeMessage] Channel {} not found in channel " - "manager ", - channelName); - return; - } - - channel->addMessage(msg); } void IrcMessageHandler::handleWriteConnectionNoticeMessage( diff --git a/src/providers/twitch/IrcMessageHandler.hpp b/src/providers/twitch/IrcMessageHandler.hpp index 40f1efb12..0474f097b 100644 --- a/src/providers/twitch/IrcMessageHandler.hpp +++ b/src/providers/twitch/IrcMessageHandler.hpp @@ -1,10 +1,12 @@ #pragma once #include +#include "messages/Message.hpp" namespace chatterino { class TwitchServer; +class Channel; class IrcMessageHandler { @@ -13,6 +15,13 @@ class IrcMessageHandler public: static IrcMessageHandler &getInstance(); + // parseMessage parses a single IRC message into 0+ Chatterino messages + std::vector parseMessage(Channel *channel, + Communi::IrcMessage *message); + + // parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages + std::vector parsePrivMessage( + Channel *channel, Communi::IrcPrivateMessage *message); void handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server); @@ -20,10 +29,22 @@ public: void handleClearChatMessage(Communi::IrcMessage *message); void handleUserStateMessage(Communi::IrcMessage *message); void handleWhisperMessage(Communi::IrcMessage *message); + + // parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+ + // chatterino messages + std::vector parseUserNoticeMessage( + Channel *channel, Communi::IrcMessage *message); void handleUserNoticeMessage(Communi::IrcMessage *message, TwitchServer &server); + void handleModeMessage(Communi::IrcMessage *message); + + // parseNoticeMessage parses a single IRC NOTICE message into 0+ chatterino + // messages + std::vector parseNoticeMessage( + Communi::IrcNoticeMessage *message); void handleNoticeMessage(Communi::IrcNoticeMessage *message); + void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message); void handleJoinMessage(Communi::IrcMessage *message); diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 9551a476c..c32fb138e 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -9,6 +9,7 @@ #include "messages/Message.hpp" #include "providers/bttv/BttvEmotes.hpp" #include "providers/bttv/LoadBttvChannelEmote.hpp" +#include "providers/twitch/IrcMessageHandler.hpp" #include "providers/twitch/PubsubClient.hpp" #include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/TwitchMessageBuilder.hpp" @@ -29,10 +30,12 @@ namespace chatterino { namespace { + // parseRecentMessages takes a json object and returns a vector of + // Communi IrcMessages auto parseRecentMessages(const QJsonObject &jsonRoot, ChannelPtr channel) { QJsonArray jsonMessages = jsonRoot.value("messages").toArray(); - std::vector messages; + std::vector messages; if (jsonMessages.empty()) return messages; @@ -40,18 +43,8 @@ namespace { for (const auto jsonMessage : jsonMessages) { auto content = jsonMessage.toString().toUtf8(); - // passing nullptr as the channel makes the message invalid but we - // don't check for that anyways - auto message = Communi::IrcMessage::fromData(content, nullptr); - auto privMsg = dynamic_cast(message); - assert(privMsg); - - MessageParseArgs args; - TwitchMessageBuilder builder(channel.get(), privMsg, args); - builder.message().flags.set(MessageFlag::RecentMessage); - - if (!builder.isIgnored()) - messages.push_back(builder.build()); + messages.emplace_back( + Communi::IrcMessage::fromData(content, nullptr)); } return messages; @@ -611,8 +604,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document) void TwitchChannel::loadRecentMessages() { - static QString genericURL = - "https://recent-messages.robotty.de/v1/recent-messages/%1"; + static QString genericURL = "https://recent-messages.robotty.de/api/v2/" + "recent-messages/%1?clearchatToNotice=true"; NetworkRequest request(genericURL.arg(this->getName())); request.setCaller(QThread::currentThread()); @@ -626,7 +619,21 @@ void TwitchChannel::loadRecentMessages() auto messages = parseRecentMessages(result.parseJson(), shared); - shared->addMessagesAtStart(messages); + auto &handler = IrcMessageHandler::getInstance(); + + std::vector allBuiltMessages; + + for (auto message : messages) + { + for (auto builtMessage : + handler.parseMessage(shared.get(), message)) + { + builtMessage->flags.set(MessageFlag::RecentMessage); + allBuiltMessages.emplace_back(builtMessage); + } + } + + shared->addMessagesAtStart(allBuiltMessages); return Success; }); diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index b8c34cc65..3f1f94b9f 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -109,12 +109,24 @@ MessagePtr TwitchMessageBuilder::build() this->appendChannelName(); + if (this->tags.contains("rm-deleted")) + { + this->message().flags.set(MessageFlag::Disabled); + } + // timestamp bool isPastMsg = this->tags.contains("historical"); if (isPastMsg) { // This may be architecture dependent(datatype) - qint64 ts = this->tags.value("tmi-sent-ts").toLongLong(); + bool customReceived = false; + qint64 ts = + this->tags.value("rm-received-ts").toLongLong(&customReceived); + if (!customReceived) + { + ts = this->tags.value("tmi-sent-ts").toLongLong(); + } + QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(ts); this->emplace(dateTime.time()); }