diff --git a/chatterino.pro b/chatterino.pro index 56ba15b98..936edc2fb 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -166,6 +166,7 @@ SOURCES += \ src/widgets/dialogs/EmotePopup.cpp \ src/widgets/dialogs/LastRunCrashDialog.cpp \ src/widgets/dialogs/LoginDialog.cpp \ + src/widgets/dialogs/LogsPopup.cpp \ src/widgets/dialogs/NotificationPopup.cpp \ src/widgets/dialogs/QualityPopup.cpp \ src/widgets/dialogs/SelectChannelDialog.cpp \ @@ -223,6 +224,7 @@ SOURCES += \ src/singletons/Settings.cpp \ src/singletons/Updates.cpp \ src/singletons/Theme.cpp \ + src/widgets/dialogs/LogsPopup.cpp \ src/controllers/moderationactions/ModerationActionModel.cpp \ src/widgets/settingspages/LookPage.cpp \ src/widgets/settingspages/FeelPage.cpp \ @@ -335,6 +337,7 @@ HEADERS += \ src/widgets/dialogs/EmotePopup.hpp \ src/widgets/dialogs/LastRunCrashDialog.hpp \ src/widgets/dialogs/LoginDialog.hpp \ + src/widgets/dialogs/LogsPopup.hpp \ src/widgets/dialogs/NotificationPopup.hpp \ src/widgets/dialogs/QualityPopup.hpp \ src/widgets/dialogs/SelectChannelDialog.hpp \ @@ -398,6 +401,7 @@ HEADERS += \ src/singletons/Theme.hpp \ src/common/SimpleSignalVector.hpp \ src/common/SignalVector.hpp \ + src/widgets/dialogs/LogsPopup.hpp \ src/common/Singleton.hpp \ src/controllers/moderationactions/ModerationActionModel.hpp \ src/widgets/settingspages/LookPage.hpp \ diff --git a/src/common/NetworkRequest.hpp b/src/common/NetworkRequest.hpp index d67a00836..a65358171 100644 --- a/src/common/NetworkRequest.hpp +++ b/src/common/NetworkRequest.hpp @@ -217,7 +217,9 @@ public: QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller, [ onFinished, data = this->data ](auto reply) mutable { if (reply->error() != QNetworkReply::NetworkError::NoError) { - // TODO: We might want to call an onError callback here + if (data.onError) { + data.onError(reply->error()); + } return; } diff --git a/src/widgets/dialogs/LogsPopup.cpp b/src/widgets/dialogs/LogsPopup.cpp new file mode 100644 index 000000000..647526ba1 --- /dev/null +++ b/src/widgets/dialogs/LogsPopup.cpp @@ -0,0 +1,143 @@ +#include "LogsPopup.hpp" + +#include "IrcMessage" +#include "common/NetworkRequest.hpp" +#include "providers/twitch/TwitchChannel.hpp" +#include "providers/twitch/TwitchMessageBuilder.hpp" +#include "widgets/helper/ChannelView.hpp" + +#include +#include +#include + +namespace chatterino { + +LogsPopup::LogsPopup() +{ + this->initLayout(); + this->resize(400, 600); +} + +void LogsPopup::initLayout() +{ + { + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + + this->channelView_ = new ChannelView(this); + layout->addWidget(this->channelView_); + + this->setLayout(layout); + } +} + +void LogsPopup::setInfo(ChannelPtr channel, QString userName) +{ + this->channel_ = channel; + this->userName_ = userName; + this->setWindowTitle(this->userName_ + "'s logs in #" + this->channel_->name); + this->getLogviewerLogs(); +} + +void LogsPopup::setMessages(std::vector &messages) +{ + ChannelPtr logsChannel(new Channel("logs", Channel::Misc)); + + logsChannel->addMessagesAtStart(messages); + this->channelView_->setChannel(logsChannel); +} + +void LogsPopup::getLogviewerLogs() +{ + TwitchChannel *twitchChannel = dynamic_cast(this->channel_.get()); + if (twitchChannel == nullptr) { + return; + } + + QString channelName = twitchChannel->name; + + QString url = QString("https://cbenni.com/api/logs/%1/?nick=%2&before=500") + .arg(channelName, this->userName_); + + NetworkRequest req(url); + req.setCaller(QThread::currentThread()); + + req.onError([this](int errorCode) { + this->getOverrustleLogs(); + return true; + }); + + req.getJSON([this, channelName](QJsonObject &data) { + + std::vector messages; + ChannelPtr logsChannel(new Channel("logs", Channel::None)); + + QJsonValue before = data.value("before"); + + for (auto i : before.toArray()) { + auto messageObject = i.toObject(); + QString message = messageObject.value("text").toString(); + + // Hacky way to fix the timestamp + message.insert(1, "historical=1;"); + message.insert(1, QString("tmi-sent-ts=%10000;").arg(messageObject["time"].toInt())); + + MessageParseArgs args; + auto ircMessage = Communi::IrcMessage::fromData(message.toUtf8(), nullptr); + auto privMsg = static_cast(ircMessage); + TwitchMessageBuilder builder(logsChannel.get(), privMsg, args); + messages.push_back(builder.build()); + }; + this->setMessages(messages); + }); + + req.execute(); +} + +void LogsPopup::getOverrustleLogs() +{ + TwitchChannel *twitchChannel = dynamic_cast(this->channel_.get()); + if (twitchChannel == nullptr) { + return; + } + + QString channelName = twitchChannel->name; + + QString url = QString("https://overrustlelogs.net/api/v1/stalk/%1/%2.json?limit=500") + .arg(channelName, this->userName_); + + NetworkRequest req(url); + req.setCaller(QThread::currentThread()); + req.onError([this, channelName](int errorCode) { + this->close(); + QMessageBox *box = new QMessageBox(QMessageBox::Information, "Error getting logs", + "No logs could be found for channel " + channelName); + box->setAttribute(Qt::WA_DeleteOnClose); + box->show(); + box->raise(); + return true; + }); + + req.getJSON([this, channelName](QJsonObject &data) { + std::vector messages; + if (data.contains("lines")) { + QJsonArray dataMessages = data.value("lines").toArray(); + for (auto i : dataMessages) { + QJsonObject singleMessage = i.toObject(); + QTime timeStamp = + QDateTime::fromSecsSinceEpoch(singleMessage.value("timestamp").toInt()).time(); + + MessagePtr message(new Message); + message->addElement(new TimestampElement(timeStamp)); + message->addElement(new TextElement(this->userName_, MessageElement::Username, + MessageColor::System)); + message->addElement(new TextElement(singleMessage.value("text").toString(), + MessageElement::Text, MessageColor::Text)); + messages.push_back(message); + } + } + this->setMessages(messages); + }); + req.execute(); +} +} // namespace chatterino diff --git a/src/widgets/dialogs/LogsPopup.hpp b/src/widgets/dialogs/LogsPopup.hpp new file mode 100644 index 000000000..e29441b1e --- /dev/null +++ b/src/widgets/dialogs/LogsPopup.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "common/Channel.hpp" +#include "widgets/BaseWindow.hpp" + +namespace chatterino { + +class Channel; +class ChannelView; + +class LogsPopup : public BaseWindow +{ +public: + LogsPopup(); + + void setInfo(std::shared_ptr channel, QString userName); + +private: + ChannelView *channelView_ = nullptr; + ChannelPtr channel_ = Channel::getEmpty(); + + QString userName_; + + void initLayout(); + void setMessages(std::vector &messages); + void getOverrustleLogs(); + void getLogviewerLogs(); +}; + +} // namespace chatterino diff --git a/src/widgets/dialogs/UserInfoPopup.cpp b/src/widgets/dialogs/UserInfoPopup.cpp index 1dd44d836..f2eee9817 100644 --- a/src/widgets/dialogs/UserInfoPopup.cpp +++ b/src/widgets/dialogs/UserInfoPopup.cpp @@ -7,6 +7,7 @@ #include "util/LayoutCreator.hpp" #include "util/PostToThread.hpp" #include "widgets/Label.hpp" +#include "widgets/dialogs/LogsPopup.hpp" #include "widgets/helper/Line.hpp" #include "widgets/helper/RippleEffectLabel.hpp" @@ -68,6 +69,8 @@ UserInfoPopup::UserInfoPopup() user.emplace("Follow").assign(&this->ui_.follow); user.emplace("Ignore").assign(&this->ui_.ignore); user.emplace("Ignore highlights").assign(&this->ui_.ignoreHighlights); + auto viewLogs = user.emplace(this).assign(&this->ui_.viewLogs); + this->ui_.viewLogs->getLabel().setText("Logs"); auto mod = user.emplace(this); mod->setPixmap(app->resources->buttons.mod); @@ -78,6 +81,13 @@ UserInfoPopup::UserInfoPopup() user->addStretch(1); + QObject::connect(viewLogs.getElement(), &RippleEffectLabel::clicked, [this] { + auto logs = new LogsPopup(); + logs->setInfo(this->channel_, this->userName_); + logs->setAttribute(Qt::WA_DeleteOnClose); + logs->show(); + }); + QObject::connect(mod.getElement(), &RippleEffectButton::clicked, [this] { this->channel_->sendMessage("/mod " + this->userName_); }); QObject::connect(unmod.getElement(), &RippleEffectButton::clicked, diff --git a/src/widgets/dialogs/UserInfoPopup.hpp b/src/widgets/dialogs/UserInfoPopup.hpp index df3cc1674..38e89fa0d 100644 --- a/src/widgets/dialogs/UserInfoPopup.hpp +++ b/src/widgets/dialogs/UserInfoPopup.hpp @@ -42,6 +42,7 @@ private: struct { RippleEffectButton *avatarButton = nullptr; + RippleEffectLabel *viewLogs = nullptr; Label *nameLabel = nullptr; Label *viewCountLabel = nullptr;