2018-08-11 12:47:03 +02:00
|
|
|
#include "Toasts.hpp"
|
|
|
|
|
|
|
|
#include "Application.hpp"
|
2023-10-17 03:50:18 +02:00
|
|
|
#include "common/Literals.hpp"
|
|
|
|
#include "common/QLogging.hpp"
|
|
|
|
#include "common/Version.hpp"
|
2018-08-11 12:47:03 +02:00
|
|
|
#include "controllers/notifications/NotificationController.hpp"
|
Sort and force grouping of includes (#4172)
This change enforces strict include grouping using IncludeCategories
In addition to adding this to the .clang-format file and applying it in the tests/src and src directories, I also did the following small changes:
In ChatterSet.hpp, I changed lrucache to a <>include
In Irc2.hpp, I change common/SignalVector.hpp to a "project-include"
In AttachedWindow.cpp, NativeMessaging.cpp, WindowsHelper.hpp, BaseWindow.cpp, and StreamerMode.cpp, I disabled clang-format for the windows-includes
In WindowDescriptors.hpp, I added the missing vector include. It was previously not needed because the include was handled by another file that was previously included first.
clang-format minimum version has been bumped, so Ubuntu version used in the check-formatting job has been bumped to 22.04 (which is the latest LTS)
2022-11-27 19:32:53 +01:00
|
|
|
#include "providers/twitch/api/Helix.hpp"
|
2019-09-15 13:02:02 +02:00
|
|
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
2018-11-25 15:02:48 +01:00
|
|
|
#include "singletons/Paths.hpp"
|
2022-12-31 15:41:01 +01:00
|
|
|
#include "singletons/Settings.hpp"
|
2019-04-22 09:03:52 +02:00
|
|
|
#include "util/StreamLink.hpp"
|
2019-04-27 10:12:51 +02:00
|
|
|
#include "widgets/helper/CommonTexts.hpp"
|
2018-08-11 12:47:03 +02:00
|
|
|
|
2018-08-12 18:54:32 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2018-08-29 19:25:37 +02:00
|
|
|
# include <wintoastlib.h>
|
2018-08-12 18:54:32 +02:00
|
|
|
#endif
|
|
|
|
|
2018-08-11 16:11:51 +02:00
|
|
|
#include <QDesktopServices>
|
2018-08-19 15:09:00 +02:00
|
|
|
#include <QFileInfo>
|
|
|
|
#include <QNetworkAccessManager>
|
|
|
|
#include <QNetworkReply>
|
2023-10-17 03:50:18 +02:00
|
|
|
#include <QStringBuilder>
|
2018-08-11 16:11:51 +02:00
|
|
|
#include <QUrl>
|
|
|
|
|
2023-10-17 03:50:18 +02:00
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
using namespace chatterino;
|
|
|
|
using namespace literals;
|
|
|
|
|
|
|
|
QString avatarFilePath(const QString &channelName)
|
|
|
|
{
|
|
|
|
// TODO: cleanup channel (to be used as a file) and use combinePath
|
|
|
|
return getPaths()->twitchProfileAvatars % '/' % channelName % u".png";
|
|
|
|
}
|
|
|
|
|
|
|
|
bool hasAvatarForChannel(const QString &channelName)
|
|
|
|
{
|
|
|
|
QFileInfo avatarFile(avatarFilePath(channelName));
|
|
|
|
return avatarFile.exists() && avatarFile.isFile();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A job that downlaods a twitch avatar and saves it to a file
|
|
|
|
class AvatarDownloader : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
public:
|
|
|
|
AvatarDownloader(const QString &avatarURL, const QString &channelName);
|
|
|
|
|
|
|
|
private:
|
|
|
|
QNetworkAccessManager manager_;
|
|
|
|
QFile file_;
|
|
|
|
QNetworkReply *reply_{};
|
|
|
|
|
|
|
|
signals:
|
|
|
|
void downloadComplete();
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace
|
2018-08-11 16:11:51 +02:00
|
|
|
|
2018-08-11 12:47:03 +02:00
|
|
|
namespace chatterino {
|
|
|
|
|
2023-10-17 03:50:18 +02:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
using WinToastLib::WinToast;
|
|
|
|
using WinToastLib::WinToastTemplate;
|
|
|
|
#endif
|
2019-04-27 13:47:13 +02:00
|
|
|
|
2018-08-12 18:54:32 +02:00
|
|
|
bool Toasts::isEnabled()
|
|
|
|
{
|
2018-09-01 13:01:54 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2023-10-17 03:50:18 +02:00
|
|
|
return WinToast::isCompatible() && getSettings()->notificationToast &&
|
2021-01-10 15:01:38 +01:00
|
|
|
!(isInStreamerMode() &&
|
2021-02-21 16:42:59 +01:00
|
|
|
getSettings()->streamerModeSuppressLiveNotifications);
|
2019-05-08 08:51:14 +02:00
|
|
|
#else
|
2018-09-01 13:01:54 +02:00
|
|
|
return false;
|
2019-05-08 08:51:14 +02:00
|
|
|
#endif
|
2018-08-12 18:54:32 +02:00
|
|
|
}
|
|
|
|
|
2019-05-01 23:21:12 +02:00
|
|
|
QString Toasts::findStringFromReaction(const ToastReaction &reaction)
|
2019-04-27 13:47:13 +02:00
|
|
|
{
|
2023-10-17 03:50:18 +02:00
|
|
|
// The constants are macros right now, but we want to avoid ASCII casts,
|
|
|
|
// so we're concatenating them with a QString literal - effectively making them part of it.
|
|
|
|
switch (reaction)
|
2019-04-27 13:47:13 +02:00
|
|
|
{
|
2023-10-17 03:50:18 +02:00
|
|
|
case ToastReaction::OpenInBrowser:
|
|
|
|
return OPEN_IN_BROWSER u""_s;
|
|
|
|
case ToastReaction::OpenInPlayer:
|
|
|
|
return OPEN_PLAYER_IN_BROWSER u""_s;
|
|
|
|
case ToastReaction::OpenInStreamlink:
|
|
|
|
return OPEN_IN_STREAMLINK u""_s;
|
|
|
|
case ToastReaction::DontOpen:
|
|
|
|
default:
|
|
|
|
return DONT_OPEN u""_s;
|
2019-04-27 13:47:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-27 18:15:41 +02:00
|
|
|
QString Toasts::findStringFromReaction(
|
2023-10-17 03:50:18 +02:00
|
|
|
const pajlada::Settings::Setting<int> &reaction)
|
2019-04-27 18:15:41 +02:00
|
|
|
{
|
2023-10-17 03:50:18 +02:00
|
|
|
static_assert(std::is_same_v<std::underlying_type_t<ToastReaction>, int>);
|
|
|
|
int value = reaction;
|
|
|
|
return Toasts::findStringFromReaction(static_cast<ToastReaction>(value));
|
2019-04-27 18:15:41 +02:00
|
|
|
}
|
|
|
|
|
2023-10-17 03:50:18 +02:00
|
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
2022-10-30 13:29:43 +01:00
|
|
|
void Toasts::sendChannelNotification(const QString &channelName,
|
|
|
|
const QString &channelTitle, Platform p)
|
2018-08-11 12:47:03 +02:00
|
|
|
{
|
2018-09-30 19:15:17 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2022-10-30 13:29:43 +01:00
|
|
|
auto sendChannelNotification = [this, channelName, channelTitle, p] {
|
|
|
|
this->sendWindowsNotification(channelName, channelTitle, p);
|
2018-09-30 19:15:17 +02:00
|
|
|
};
|
|
|
|
#else
|
2023-10-17 03:50:18 +02:00
|
|
|
(void)channelTitle;
|
2018-09-30 19:15:17 +02:00
|
|
|
auto sendChannelNotification = [] {
|
2023-04-16 21:05:51 +02:00
|
|
|
// Unimplemented for macOS and Linux
|
2018-09-30 19:15:17 +02:00
|
|
|
};
|
|
|
|
#endif
|
2018-08-19 19:02:49 +02:00
|
|
|
// Fetch user profile avatar
|
|
|
|
if (p == Platform::Twitch)
|
|
|
|
{
|
2023-10-17 03:50:18 +02:00
|
|
|
if (hasAvatarForChannel(channelName))
|
2018-08-19 19:02:49 +02:00
|
|
|
{
|
2018-09-30 19:15:17 +02:00
|
|
|
sendChannelNotification();
|
2018-08-19 19:02:49 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-14 12:13:57 +01:00
|
|
|
getHelix()->getUserByName(
|
2018-09-30 19:15:17 +02:00
|
|
|
channelName,
|
2020-03-14 12:13:57 +01:00
|
|
|
[channelName, sendChannelNotification](const auto &user) {
|
2023-10-17 03:50:18 +02:00
|
|
|
// gets deleted when finished
|
|
|
|
auto *downloader =
|
|
|
|
new AvatarDownloader(user.profileImageUrl, channelName);
|
|
|
|
QObject::connect(downloader,
|
|
|
|
&AvatarDownloader::downloadComplete,
|
2018-09-30 19:15:17 +02:00
|
|
|
sendChannelNotification);
|
2020-03-14 12:13:57 +01:00
|
|
|
},
|
|
|
|
[] {
|
|
|
|
// on failure
|
2018-08-19 19:02:49 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2018-08-11 12:47:03 +02:00
|
|
|
}
|
|
|
|
|
2018-08-11 16:11:51 +02:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
|
|
|
class CustomHandler : public WinToastLib::IWinToastHandler
|
|
|
|
{
|
2018-08-19 15:09:00 +02:00
|
|
|
private:
|
|
|
|
QString channelName_;
|
|
|
|
Platform platform_;
|
|
|
|
|
2018-08-11 16:11:51 +02:00
|
|
|
public:
|
2018-08-19 15:09:00 +02:00
|
|
|
CustomHandler(QString channelName, Platform p)
|
2023-10-17 03:50:18 +02:00
|
|
|
: channelName_(std::move(channelName))
|
2018-08-19 15:09:00 +02:00
|
|
|
, platform_(p)
|
|
|
|
{
|
|
|
|
}
|
2023-10-17 03:50:18 +02:00
|
|
|
void toastActivated() const override
|
2018-08-11 16:11:51 +02:00
|
|
|
{
|
2019-05-10 23:31:10 +02:00
|
|
|
auto toastReaction =
|
|
|
|
static_cast<ToastReaction>(getSettings()->openFromToast.getValue());
|
|
|
|
|
|
|
|
switch (toastReaction)
|
2019-04-22 09:03:52 +02:00
|
|
|
{
|
2019-05-01 23:21:12 +02:00
|
|
|
case ToastReaction::OpenInBrowser:
|
2019-04-27 13:47:13 +02:00
|
|
|
if (platform_ == Platform::Twitch)
|
|
|
|
{
|
2023-10-17 03:50:18 +02:00
|
|
|
QDesktopServices::openUrl(
|
|
|
|
QUrl(u"https://www.twitch.tv/" % channelName_));
|
2019-04-27 13:47:13 +02:00
|
|
|
}
|
|
|
|
break;
|
2019-05-01 23:21:12 +02:00
|
|
|
case ToastReaction::OpenInPlayer:
|
2019-04-27 13:47:13 +02:00
|
|
|
if (platform_ == Platform::Twitch)
|
|
|
|
{
|
2023-10-17 03:50:18 +02:00
|
|
|
QDesktopServices::openUrl(QUrl(
|
|
|
|
u"https://player.twitch.tv/?parent=twitch.tv&channel=" %
|
|
|
|
channelName_));
|
2019-04-27 13:47:13 +02:00
|
|
|
}
|
|
|
|
break;
|
2019-09-26 00:51:05 +02:00
|
|
|
case ToastReaction::OpenInStreamlink: {
|
2019-04-27 13:47:13 +02:00
|
|
|
openStreamlinkForChannel(channelName_);
|
|
|
|
break;
|
2019-04-22 09:03:52 +02:00
|
|
|
}
|
2023-10-17 03:50:18 +02:00
|
|
|
case ToastReaction::DontOpen:
|
|
|
|
// nothing should happen
|
|
|
|
break;
|
2019-04-22 09:03:52 +02:00
|
|
|
}
|
2018-08-11 16:11:51 +02:00
|
|
|
}
|
|
|
|
|
2023-10-17 03:50:18 +02:00
|
|
|
void toastActivated(int actionIndex) const override
|
2018-08-11 16:11:51 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-10-17 03:50:18 +02:00
|
|
|
void toastFailed() const override
|
2018-08-11 16:11:51 +02:00
|
|
|
{
|
|
|
|
}
|
2018-08-19 15:09:00 +02:00
|
|
|
|
2023-10-17 03:50:18 +02:00
|
|
|
void toastDismissed(WinToastDismissalReason state) const override
|
2018-08-11 16:11:51 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-10-17 03:50:18 +02:00
|
|
|
void Toasts::ensureInitialized()
|
|
|
|
{
|
|
|
|
if (this->initialized_)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this->initialized_ = true;
|
|
|
|
|
|
|
|
auto *instance = WinToast::instance();
|
|
|
|
instance->setAppName(L"Chatterino2");
|
|
|
|
instance->setAppUserModelId(
|
|
|
|
WinToast::configureAUMI(L"", L"Chatterino 2", L"",
|
|
|
|
Version::instance().version().toStdWString()));
|
|
|
|
instance->setShortcutPolicy(WinToast::SHORTCUT_POLICY_IGNORE);
|
|
|
|
WinToast::WinToastError error{};
|
|
|
|
instance->initialize(&error);
|
|
|
|
|
|
|
|
if (error != WinToast::NoError)
|
|
|
|
{
|
|
|
|
qCDebug(chatterinoNotification)
|
|
|
|
<< "Failed to initialize WinToast - error:" << error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-30 13:29:43 +01:00
|
|
|
void Toasts::sendWindowsNotification(const QString &channelName,
|
|
|
|
const QString &channelTitle, Platform p)
|
2018-08-11 16:11:51 +02:00
|
|
|
{
|
2023-10-17 03:50:18 +02:00
|
|
|
this->ensureInitialized();
|
|
|
|
|
|
|
|
WinToastTemplate templ(WinToastTemplate::ImageAndText03);
|
|
|
|
QString str = channelName % u" is live!";
|
2018-08-11 16:11:51 +02:00
|
|
|
|
2023-10-17 03:50:18 +02:00
|
|
|
templ.setTextField(str.toStdWString(), WinToastTemplate::FirstLine);
|
2019-05-01 23:21:12 +02:00
|
|
|
if (static_cast<ToastReaction>(getSettings()->openFromToast.getValue()) !=
|
|
|
|
ToastReaction::DontOpen)
|
2019-04-27 10:12:51 +02:00
|
|
|
{
|
2019-04-27 18:15:41 +02:00
|
|
|
QString mode =
|
|
|
|
Toasts::findStringFromReaction(getSettings()->openFromToast);
|
2019-04-27 10:12:51 +02:00
|
|
|
mode = mode.toLower();
|
2019-04-22 09:03:52 +02:00
|
|
|
|
2023-10-17 03:50:18 +02:00
|
|
|
templ.setTextField(
|
|
|
|
u"%1 \nClick to %2"_s.arg(channelTitle).arg(mode).toStdWString(),
|
|
|
|
WinToastTemplate::SecondLine);
|
2019-04-22 09:03:52 +02:00
|
|
|
}
|
|
|
|
|
2023-10-17 03:50:18 +02:00
|
|
|
QString avatarPath;
|
2018-08-19 15:09:00 +02:00
|
|
|
if (p == Platform::Twitch)
|
|
|
|
{
|
2023-10-17 03:50:18 +02:00
|
|
|
avatarPath = avatarFilePath(channelName);
|
2018-08-19 15:09:00 +02:00
|
|
|
}
|
2023-10-17 03:50:18 +02:00
|
|
|
templ.setImagePath(avatarPath.toStdWString());
|
2018-08-29 19:25:37 +02:00
|
|
|
if (getSettings()->notificationPlaySound)
|
|
|
|
{
|
2023-10-17 03:50:18 +02:00
|
|
|
templ.setAudioOption(WinToastTemplate::AudioOption::Silent);
|
|
|
|
}
|
|
|
|
|
|
|
|
WinToast::WinToastError error = WinToast::NoError;
|
|
|
|
WinToast::instance()->showToast(templ, new CustomHandler(channelName, p),
|
|
|
|
&error);
|
|
|
|
if (error != WinToast::NoError)
|
|
|
|
{
|
|
|
|
qCWarning(chatterinoNotification) << "Failed to show toast:" << error;
|
2018-08-12 20:21:21 +02:00
|
|
|
}
|
2018-08-11 16:11:51 +02:00
|
|
|
}
|
2018-08-19 15:09:00 +02:00
|
|
|
|
2018-08-11 16:11:51 +02:00
|
|
|
#endif
|
2018-08-19 15:09:00 +02:00
|
|
|
|
2018-08-11 12:47:03 +02:00
|
|
|
} // namespace chatterino
|
2023-10-17 03:50:18 +02:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
AvatarDownloader::AvatarDownloader(const QString &avatarURL,
|
|
|
|
const QString &channelName)
|
|
|
|
: file_(avatarFilePath(channelName))
|
|
|
|
{
|
|
|
|
if (!this->file_.open(QFile::WriteOnly | QFile::Truncate))
|
|
|
|
{
|
|
|
|
qCWarning(chatterinoNotification)
|
|
|
|
<< "Failed to open avatar file" << this->file_.errorString();
|
|
|
|
}
|
|
|
|
|
|
|
|
this->reply_ = this->manager_.get(QNetworkRequest(avatarURL));
|
|
|
|
|
|
|
|
connect(this->reply_, &QNetworkReply::readyRead, this, [this] {
|
|
|
|
this->file_.write(this->reply_->readAll());
|
|
|
|
});
|
|
|
|
connect(this->reply_, &QNetworkReply::finished, this, [this] {
|
|
|
|
if (this->reply_->error() != QNetworkReply::NoError)
|
|
|
|
{
|
|
|
|
qCWarning(chatterinoNotification)
|
|
|
|
<< "Failed to download avatar" << this->reply_->errorString();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this->file_.isOpen())
|
|
|
|
{
|
|
|
|
this->file_.close();
|
|
|
|
}
|
|
|
|
emit downloadComplete();
|
|
|
|
this->deleteLater();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "Toasts.moc"
|
|
|
|
|
|
|
|
} // namespace
|