Initial commit

This commit is contained in:
snow flurry 2024-06-16 15:09:03 -07:00
commit 0765a9801b
13 changed files with 899 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.vscode/
build

71
CMakeLists.txt Normal file
View file

@ -0,0 +1,71 @@
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
project(kmail-unsubscribe VERSION "1.0.0")
set(KF_MIN_VERSION "6.0.0")
set(QT_REQUIRED_VERSION "6.6.0")
# Extra CMake Modules
find_package(ECM ${KF_MIN_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${ECM_MODULE_DIR} ${ECM_KDE_MODULE_DIR})
include(ECMQtDeclareLoggingCategory)
include(KDEInstallDirs)
include(KDECMakeSettings)
find_package(Qt6 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Network Widgets)
find_package(KF6Config ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KF6GuiAddons ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KF6XmlGui ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KF6Parts ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KF6KIO ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KPim6MailCommon ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KPim6MessageCore ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KPim6MessageViewer ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KPim6Libkdepim ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KPim6PimCommonAkonadi ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KF6I18n ${KF_MIN_VERSION} NO_MODULE)
ki18n_install(po)
set(kmail_unsubscribe_SRCS
unsubscribemanager.cpp
unsubscribemanager.h
unsubscribeplugin.cpp
unsubscribeplugin.h
unsubscribeplugininterface.cpp
unsubscribeplugininterface.h
oneclickunsubscribejob.cpp
oneclickunsubscribejob.h
)
ecm_qt_declare_logging_category(
kmail_unsubscribe_SRCS
HEADER "unsubscribe_debug.h"
IDENTIFIER "UnsubscribePlugin"
CATEGORY_NAME "xyz.datagirl.kpim.unsubscribe"
DESCRIPTION "Unsubscribe Plugin"
DEFAULT_SEVERITY Info
EXPORT
)
set(BUILD_SHARED_LIBS ON)
kcoreaddons_add_plugin(kmail_unsubscribe
SOURCES ${kmail_unsubscribe_SRCS}
INSTALL_NAMESPACE pim6/messageviewer/viewerplugin)
target_link_libraries(kmail_unsubscribe
KPim6::PimCommon
KPim6::Libkdepim
KPim6::PimCommonAkonadi
KPim6::MessageViewer
KF6::XmlGui
KF6::KIOCore
KF6::KIOGui
KF6::KIOWidgets
KF6::I18n)
set_target_properties(kmail_unsubscribe PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)

39
build-msgs.sh Executable file
View file

@ -0,0 +1,39 @@
#!/usr/bin/env bash
APPNAME="kmail_unsubscribe"
DEFAULT_XGETTEXT="$(command -v xgettext)"
XGETTEXT_PROGRAM="${XGETTEXT:-${DEFAULT_XGETTEXT}}"
if [ -z "$XGETTEXT_PROGRAM" ] ; then
echo "error: Couldn't find xgettext. Set \$XGETTEXT to its path, or make sure you have gettext installed." >&2
exit 1
fi
# Pulled from https://invent.kde.org/sysadmin/l10n-scripty/-/blob/master/extract-messages.sh
do_xgettext() {
$XGETTEXT_PROGRAM --copyright-holder="snow flurry" \
--package-name=$APPNAME \
--msgid-bugs-address=https://git.2ki.xyz/snow/kmail_unsubscribe \
--from-code=UTF-8 \
-C --kde \
-ci18n \
-ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 \
-ki18nd:2 -ki18ndc:2c,3 -ki18ndp:2,3 -ki18ndcp:2c,3,4 \
-kki18n:1 -kki18nc:1c,2 -kki18np:1,2 -kki18ncp:1c,2,3 \
-kki18nd:2 -kki18ndc:2c,3 -kki18ndp:2,3 -kki18ndcp:2c,3,4 \
-kxi18n:1 -kxi18nc:1c,2 -kxi18np:1,2 -kxi18ncp:1c,2,3 \
-kxi18nd:2 -kxi18ndc:2c,3 -kxi18ndp:2,3 -kxi18ndcp:2c,3,4 \
-kkxi18n:1 -kkxi18nc:1c,2 -kkxi18np:1,2 -kkxi18ncp:1c,2,3 \
-kkxi18nd:2 -kkxi18ndc:2c,3 -kkxi18ndp:2,3 -kkxi18ndcp:2c,3,4 \
-kkli18n:1 -kkli18nc:1c,2 -kkli18np:1,2 -kkli18ncp:1c,2,3 \
-kklxi18n:1 -kklxi18nc:1c,2 -kklxi18np:1,2 -kklxi18ncp:1c,2,3 \
-kI18N_NOOP:1 -kI18NC_NOOP:1c,2 \
-kI18N_NOOP2:1c,2 -kI18N_NOOP2_NOSTRIP:1c,2 \
-ktr2i18n:1 -ktr2xi18n:1 \
"$@"
}
SRC_ROOT="$(dirname "${BASH_SOURCE[0]}")"
do_xgettext `find "${SRC_ROOT}" \( -name \*.cpp -o -name \*.h -o -name \*.qml \)` -o "${SRC_ROOT}/po/${APPNAME}.pot"

View file

@ -0,0 +1,8 @@
{
"KPlugin": {
"Description": "Adds RFC 8058 One-Click Unsubscribe to KMail.",
"EnabledByDefault": true,
"Name": "Unsubscribe",
"Version": "2.0"
}
}

View file

@ -0,0 +1,68 @@
#include "oneclickunsubscribejob.h"
#include <QNetworkReply>
#include <QHttpMultiPart>
#include "unsubscribe_debug.h"
using namespace MessageViewer;
OneClickUnsubscribeJob::OneClickUnsubscribeJob(QUrl &oneClickUrl, UnsubscribeManager *parent)
: QObject(parent),
mNetworkAccessManager(new QNetworkAccessManager(this))
{
mNetworkAccessManager->setStrictTransportSecurityEnabled(true);
mNetworkAccessManager->enableStrictTransportSecurityStore(true);
connect(mNetworkAccessManager, &QNetworkAccessManager::finished, this, &OneClickUnsubscribeJob::slotFinished);
connect(mNetworkAccessManager, &QNetworkAccessManager::sslErrors, this, &OneClickUnsubscribeJob::slotSslErrors);
mUrl = oneClickUrl;
}
void OneClickUnsubscribeJob::start()
{
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
QHttpPart mainPart;
// Per RFC 8058, only "List-Unsubscribe=One-Click" is allowed. To avoid
// parsing issues, we simply won't parse anything
mainPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"List-Unsubscribe\""));
mainPart.setBody("One-Click");
multiPart->append(mainPart);
QNetworkRequest request(mUrl);
qCDebug(UnsubscribePlugin) << "Sending one-click unsubscribe request to" << mUrl;
mReply = mNetworkAccessManager->post(request, multiPart);
connect(mReply, &QNetworkReply::errorOccurred, this, &OneClickUnsubscribeJob::slotError);
}
void OneClickUnsubscribeJob::slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error)
{
// TODO: allow override somehow
qCDebug(UnsubscribePlugin) << "Got" << error.count() << "SSL error(s)";
UnsubscribeManager::Result sslErrResult = {
.Type = UnsubscribeManager::Result::SslError,
.ErrorString = error.first().errorString(),
};
Q_EMIT result(sslErrResult);
}
void OneClickUnsubscribeJob::slotFinished(QNetworkReply *reply)
{
qCDebug(UnsubscribePlugin) << "Successful response from" << mUrl << "with result" << reply->errorString();
UnsubscribeManager::Result successResult = {
.Type = UnsubscribeManager::Result::None,
};
Q_EMIT result(successResult);
}
void OneClickUnsubscribeJob::slotError(QNetworkReply::NetworkError error)
{
UnsubscribeManager::Result errorResult = {
.Type = UnsubscribeManager::Result::NetworkError,
.ErrorString = mReply->errorString(),
};
Q_EMIT result(errorResult);
}
#include "moc_oneclickunsubscribejob.cpp"

49
oneclickunsubscribejob.h Normal file
View file

@ -0,0 +1,49 @@
#ifndef _ONECLICKUNSUBSCRIBEJOB_H_
#define _ONECLICKUNSUBSCRIBEJOB_H_
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include "unsubscribemanager.h"
namespace MessageViewer
{
class OneClickUnsubscribeJob : public QObject
{
Q_OBJECT
public:
explicit OneClickUnsubscribeJob(QUrl &oneClickUrl, UnsubscribeManager *parent);
~OneClickUnsubscribeJob() = default;
/**
* @brief Starts the unsubscribe job.
*
*/
void start();
// Slots are used for connection to mNetworkAccessManager
public slots:
void slotFinished(QNetworkReply *reply);
void slotSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
void slotError(QNetworkReply::NetworkError error);
signals:
/**
* @brief Triggered on job completion/failure.
*
* @param success Whether the job was successful. If true, sslError and error should be ignored.
* @param sslError If unsuccessful, whether one or more SSL errors occurred.
* @param error If unsuccessful, the error code returned by the QNetworkRequest.
*/
void result(const UnsubscribeManager::Result &data);
private:
QNetworkAccessManager *const mNetworkAccessManager;
QUrl mUrl;
bool sentResult = false;
QNetworkReply *mReply = nullptr;
};
}
#endif /* !_ONECLICKUNSUBSCRIBEJOB_H_ */

100
po/kmail_unsubscribe.pot Normal file
View file

@ -0,0 +1,100 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR snow flurry
# This file is distributed under the same license as the kmail_unsubscribe package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: kmail_unsubscribe\n"
"Report-Msgid-Bugs-To: https://git.2ki.xyz/snow/kmail_unsubscribe\n"
"POT-Creation-Date: 2024-06-13 00:32-0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: unsubscribemanager.cpp:178
#, kde-format
msgid "Unable to send unsubscribe request: %1"
msgstr ""
#: unsubscribemanager.cpp:181
#, kde-format
msgid "Got one or more SSL errors: %1"
msgstr ""
#: unsubscribemanager.cpp:184
#, kde-format
msgid "Plugin hit an unreachable point"
msgstr ""
#: unsubscribeplugininterface.cpp:59
#, kde-format
msgid "Network not available"
msgstr ""
#: unsubscribeplugininterface.cpp:60
#, kde-format
msgid "Please go back online to unsubscribe from this list."
msgstr ""
#: unsubscribeplugininterface.cpp:72
#, kde-format
msgid "Can't Unsubscribe"
msgstr ""
#: unsubscribeplugininterface.cpp:73
#, kde-format
msgid "This email doesn't advertise a way to unsubscribe."
msgstr ""
#: unsubscribeplugininterface.cpp:93
#, kde-format
msgid "The digital signature of this email couldn't be validated."
msgstr ""
#: unsubscribeplugininterface.cpp:94
#, kde-format
msgid "Do you still want to unsubscribe?"
msgstr ""
#: unsubscribeplugininterface.cpp:104
#, kde-format
msgid "This mailing list supports One-Click Unsubscribe."
msgstr ""
#: unsubscribeplugininterface.cpp:105
#, kde-format
msgid "Do you want to unsubscribe?"
msgstr ""
#: unsubscribeplugininterface.cpp:145
#, kde-format
msgid "Unsubscribe"
msgstr ""
#: unsubscribeplugininterface.cpp:146
#, kde-format
msgid ""
"Allows you to unsubscribe from a mailing list, if the sender supports One-"
"Click Unsubscribe"
msgstr ""
#: unsubscribeplugininterface.cpp:158
#, kde-format
msgid "Request Complete"
msgstr ""
#: unsubscribeplugininterface.cpp:159
#, kde-format
msgid "The unsubscribe request was successfully sent."
msgstr ""
#: unsubscribeplugininterface.cpp:164
#, kde-format
msgid "Unsubscribe Error"
msgstr ""

190
unsubscribemanager.cpp Normal file
View file

@ -0,0 +1,190 @@
#include <KLocalizedString>
#include "unsubscribe_debug.h"
#include "unsubscribemanager.h"
#include "oneclickunsubscribejob.h"
using namespace MessageViewer;
UnsubscribeManager::UnsubscribeManager(QObject *parent)
: QObject(parent),
mDkimMgr(DKIMManager(this))
{
connect(&mDkimMgr, &DKIMManager::result, this, &UnsubscribeManager::getDkimResult);
}
UnsubscribeManager::~UnsubscribeManager() = default;
void UnsubscribeManager::reset()
{
mDKIMValid = false;
mItemId = -1;
mPostUrl = QUrl();
mMessage = nullptr;
}
void UnsubscribeManager::setMessageItem(const Akonadi::Item &item)
{
if (item.id() == mItemId)
{
// We already have this item, do nothing
return;
}
else if (mItemId != -1)
{
// reset wasn't called first, so call it for them
this->reset();
}
// First, we have to have a KMime::Message::Ptr
if (item.hasPayload<KMime::Message::Ptr>())
{
mMessage = item.payload<KMime::Message::Ptr>();
if (mMessage == nullptr)
{
// Sometimes we get nullptr even though item.hasPayload() was true...
qCInfo(UnsubscribePlugin) << "Can't get current email";
return;
}
mItemId = item.id();
// Check if we even have Unsubscribe info
mList = MessageCore::MailingList::detect(mMessage);
}
else
{
qWarning(UnsubscribePlugin) << "Received email doesn't seem to be an email";
}
// Don't bother checking DKIM if we don't have an unsubscribe URL, or if we
// don't have DKIM enabled
if (!(getUrl().isEmpty()) && MessageViewerSettings::self()->enabledDkim())
{
mDkimMgr.checkDKim(item);
}
}
bool UnsubscribeManager::hasMessage()
{
return !(mMessage == nullptr);
}
void UnsubscribeManager::getDkimResult(const MessageViewer::DKIMCheckSignatureJob::CheckSignatureResult &checkResult, Akonadi::Item::Id id)
{
if (id == mItemId)
{
mDKIMValid = checkResult.isValid();
qCDebug(UnsubscribePlugin) << "Got DKIM result! Valid:" << mDKIMValid;
}
else
{
qCDebug(UnsubscribePlugin) << "Got DKIM result for wrong ID! Wanted" << mItemId << "but got" << id;
}
}
UnsubscribeManager::Status
UnsubscribeManager::unsubscribeStatus()
{
if (mMessage != nullptr && mList.features().testFlag(MessageCore::MailingList::Unsubscribe))
{
if (!oneClickUrl().isEmpty())
{
// One-Click requires List-Unsubscribe-Post
if (mMessage->hasHeader(LIST_UNSUBSCRIBE_POST_HDR))
{
if (MessageViewerSettings::self()->enabledDkim() &&
!mDKIMValid)
{
return UnsubscribeManager::InvalidOneClick;
}
return UnsubscribeManager::ValidOneClick;
}
}
return UnsubscribeManager::NoOneClick;
}
return UnsubscribeManager::None;
}
void UnsubscribeManager::doOneClick()
{
auto status = unsubscribeStatus();
if (status == UnsubscribeManager::InvalidOneClick || status == UnsubscribeManager::ValidOneClick)
{
auto job = new OneClickUnsubscribeJob(mPostUrl, this);
connect(job, &OneClickUnsubscribeJob::result, this, &UnsubscribeManager::checkResult);
job->start();
}
}
QUrl UnsubscribeManager::oneClickUrl()
{
if (mPostUrl.isEmpty() && mList.features().testFlag(MessageCore::MailingList::Unsubscribe))
{
foreach (QUrl url, mList.unsubscribeUrls())
{
// Per the RFC, there should only be one HTTPS URL. So grab the
// first one we find
if (url.scheme() == QStringLiteral("https"))
{
mPostUrl = url;
break;
}
}
}
return mPostUrl;
}
QUrl UnsubscribeManager::getUrl()
{
// TODO: Maybe this should be customizable.
// The theory is that HTTPS is most secure, mailto is more likely to be
// secure (MTAs often use TLS these days), and http as last resort. I really
// hope nobody's requesting unsubscribe over IRC...
const QStringList protocols = {"https", "mailto", "http"};
if (!mPostUrl.isEmpty())
{
return mPostUrl;
}
else
{
foreach (QString scheme, protocols)
{
foreach (QUrl url, mList.unsubscribeUrls())
{
if (url.scheme() == scheme)
{
return url;
}
}
}
}
return QUrl();
}
void UnsubscribeManager::checkResult(const Result &result)
{
bool isSuccess = false;
QString resultStr;
switch (result.Type)
{
case Result::None:
isSuccess = true;
break;
case Result::NetworkError:
resultStr = i18n("Unable to send unsubscribe request: %1", result.ErrorString);
break;
case Result::SslError:
resultStr = i18n("Got one or more SSL errors: %1", result.ErrorString);
break;
default:
resultStr = i18n("Plugin hit an unreachable point");
}
Q_EMIT oneClickResult(isSuccess, resultStr);
}
#include "moc_unsubscribemanager.cpp"

127
unsubscribemanager.h Normal file
View file

@ -0,0 +1,127 @@
#ifndef _UNSUBSCRIBEMANAGER_H_
#define _UNSUBSCRIBEMANAGER_H_
#include <QObject>
#include <Akonadi/Item>
#include <KMime/KMimeMessage>
#include <MessageCore/MailingList>
#include <MessageViewer/DKIMManager>
#define LIST_UNSUBSCRIBE_POST_HDR "List-Unsubscribe-Post"
#define LIST_UNSUBSCRIBE_POST_VALUE "List-Unsubscribe=One-Click"
namespace MessageViewer
{
class UnsubscribeManager : public QObject
{
Q_OBJECT
public:
explicit UnsubscribeManager(QObject *parent = nullptr);
~UnsubscribeManager() override;
struct Result
{
enum _errorType
{
None = 0,
NetworkError,
SslError,
} Type;
QString ErrorString;
};
enum Status
{
/// @brief No unsubscribe method is available.
None,
/// @brief Unsubscribe is available, but not as one-click unsubscribe.
NoOneClick,
/**
* @brief One-Click Unsubscribe is available, but DKIM didn't verify.
*
* Note: This state is considered non-compliant with RFC 8058.
*/
InvalidOneClick,
/// @brief One-Click Unsubscribe is available.
ValidOneClick,
};
/**
* @brief Sets the current message item.
*
* @param item The current message item.
*/
void setMessageItem(const Akonadi::Item &item);
/**
* @brief Tests whether the current message item has been set.
*/
bool hasMessage();
/**
* @brief Tests if the message has information to programmatically unsubscribe
*
* @return true if the message can be unsubscribed from.
* @return false if the message cannot be unsubscribed from.
*/
Status unsubscribeStatus();
/**
* @brief Performs a One-Click unsubscribe.
*/
void doOneClick();
/**
* @brief Get the URL for One-Click Unsubscribe.
*
* @return QUrl The URL. If none was found, the URL will be empty.
*/
QUrl oneClickUrl();
/**
* @brief Get the best Unsubscribe URL.
* Unlike oneClickUrl, this will check for any usable Unsubscribe URL.
* The search order is currently HTTPS, MAILTO, and HTTP URLs.
*
* @return QUrl The best available Unsubscribe URL, per the above heuristic
*/
QUrl getUrl();
/**
* @brief Resets the object's state.
* @remark This does not cancel any running One-Click Unsubscribe jobs.
*
*/
void reset();
public slots:
/**
* @brief This is used as a slot for DKIMManager::result.
* This signal can only be signalled once-- further signals will be
* ignored.
*
* @param checkResult
* @param id
*/
void getDkimResult(const MessageViewer::DKIMCheckSignatureJob::CheckSignatureResult &checkResult, Akonadi::Item::Id id);
void checkResult(const Result &result);
signals:
void oneClickResult(bool isSuccess, const QString &resultString);
private:
// message info
KMime::Message::Ptr mMessage = nullptr;
MessageCore::MailingList mList;
Akonadi::Item::Id mItemId = -1;
// Cached by oneClickUrl()
QUrl mPostUrl;
// Used to check DKIM, for RFC 8058 compliance
DKIMManager mDkimMgr;
bool mDKIMValid = false;
};
}
#endif /* !_UNSUBSCRIBEMANAGER_H_ */

21
unsubscribeplugin.cpp Normal file
View file

@ -0,0 +1,21 @@
#include "unsubscribeplugin.h"
#include "unsubscribeplugininterface.h"
#include <KActionCollection>
#include <KPluginFactory>
using namespace MessageViewer;
K_PLUGIN_CLASS_WITH_JSON(UnsubscribePlugin, "kmail_unsubscribeplugin.json")
UnsubscribePlugin::UnsubscribePlugin(QObject *parent, const QList<QVariant> &)
: MessageViewer::ViewerPlugin(parent)
{
}
ViewerPluginInterface *MessageViewer::UnsubscribePlugin::createView(QWidget *parent, KActionCollection *ac)
{
MessageViewer::ViewerPluginInterface *view = new MessageViewer::UnsubscribePluginInterface(parent, ac);
return view;
}
#include "unsubscribeplugin.moc"
#include "moc_unsubscribeplugin.cpp"

23
unsubscribeplugin.h Normal file
View file

@ -0,0 +1,23 @@
#ifndef _UNSUBSCRIBEPLUGIN_H
#define _UNSUBSCRIBEPLUGIN_H
#include <MessageViewer/ViewerPlugin>
#include <QVariant>
namespace MessageViewer
{
class UnsubscribePlugin : public MessageViewer::ViewerPlugin
{
Q_OBJECT
public:
explicit UnsubscribePlugin(QObject *parent = nullptr, const QList<QVariant> & = QList<QVariant>());
[[nodiscard]] ViewerPluginInterface *createView(QWidget *parent, KActionCollection *ac) override;
[[nodiscard]] QString viewerPluginName() const override
{
return QStringLiteral("oneclick-unsubscribe");
};
};
}
#endif

View file

@ -0,0 +1,162 @@
#include "unsubscribe_debug.h"
#include "unsubscribeplugininterface.h"
#include <QMessageBox>
#include <KActionCollection>
#include <KLocalizedString>
#include <MessageCore/MailingList>
#include <PimCommon/NetworkManager>
#include <KIO/OpenUrlJob>
#include <KIO/JobUiDelegate>
#include <KIO/JobUiDelegateFactory>
using namespace MessageViewer;
// General yes/no confirm dialog
static bool
confirmDialog(const QString &text, const QString &question, bool safe)
{
QMessageBox msgBox;
msgBox.setText(text);
msgBox.setInformativeText(question);
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setIcon(safe ? QMessageBox::Question : QMessageBox::Warning);
msgBox.setDefaultButton(safe ? QMessageBox::Yes : QMessageBox::No);
return msgBox.exec() == QMessageBox::Yes;
}
UnsubscribePluginInterface::UnsubscribePluginInterface(QWidget *parent, KActionCollection *ac)
: ViewerPluginInterface(parent),
mParent(parent)
{
if (ac)
{
// Create the action...
auto action = new QAction(this);
action->setIcon(QIcon::fromTheme(QStringLiteral("news-unsubscribe")));
action->setIconText(i18n("Unsubscribe"));
action->setWhatsThis(i18n("Allows you to unsubscribe from a mailing list, if the sender supports One-Click Unsubscribe"));
// ... and add it to the application's collection
ac->addAction(QStringLiteral("oneclick_unsubscribe"), action);
connect(action, &QAction::triggered, this, &UnsubscribePluginInterface::slotActivatePlugin);
// also add it to our list, for actions()
mActions.append(action);
}
connect(&mUnsub, &UnsubscribeManager::oneClickResult, this, &UnsubscribePluginInterface::getOneClickResult);
}
UnsubscribePluginInterface::~UnsubscribePluginInterface() = default;
QList<QAction *> UnsubscribePluginInterface::actions() const
{
return mActions;
}
void UnsubscribePluginInterface::closePlugin()
{
// Reset the UnsubscribeManager's state.
// XXX: Currently, this doesn't cancel any running One-Click Unsubscribe
// calls!
mUnsub.reset();
}
/// @brief Called when the action is clicked or otherwise activated
void UnsubscribePluginInterface::execute()
{
qCDebug(UnsubscribePlugin) << "-click!-";
// short-circuit if we're offline
if (!PimCommon::NetworkManager::self()->isOnline())
{
QMessageBox::critical(mParent,
i18n("Network not available"),
i18n("Please go back online to unsubscribe from this list."));
return;
}
if (mUnsub.hasMessage())
{
switch (mUnsub.unsubscribeStatus())
{
case UnsubscribeManager::None:
{
// Should not happen
QMessageBox::warning(mParent,
i18n("Can't Unsubscribe"),
i18n("This email doesn't advertise a way to unsubscribe."));
break;
}
break;
case UnsubscribeManager::NoOneClick:
{
// Load the unsubscribe URL normally
QUrl url = mUnsub.getUrl();
if (!url.isEmpty())
{
auto job = new KIO::OpenUrlJob(url);
job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, mParent));
job->start();
}
break;
}
break;
case UnsubscribeManager::InvalidOneClick:
{
if (confirmDialog(
i18n("The digital signature of this email couldn't be validated."),
i18n("Do you still want to unsubscribe?"),
false))
{
mUnsub.doOneClick();
}
break;
}
case UnsubscribeManager::ValidOneClick:
{
if (confirmDialog(
i18n("This mailing list supports One-Click Unsubscribe."),
i18n("Do you want to unsubscribe?"),
true))
{
mUnsub.doOneClick();
}
break;
}
}
}
}
void UnsubscribePluginInterface::setMessageItem(const Akonadi::Item &item)
{
mUnsub.setMessageItem(item);
}
/// @brief Triggered when the selected item changes.
/// @param item The new item.
void UnsubscribePluginInterface::updateAction(const Akonadi::Item &item)
{
qCDebug(UnsubscribePlugin) << "updateAction!?" << mActions.count() << "actions";
mUnsub.setMessageItem(item);
mActions.first()->setDisabled(mUnsub.unsubscribeStatus() == UnsubscribeManager::None);
}
void UnsubscribePluginInterface::getOneClickResult(bool isSuccess, const QString &resultString)
{
if (isSuccess)
{
QMessageBox::information(mParent,
i18n("Request Complete"),
i18n("The unsubscribe request was successfully sent."));
}
else
{
QMessageBox::critical(mParent,
i18n("Unsubscribe Error"),
resultString);
}
}
#include "moc_unsubscribeplugininterface.cpp"

View file

@ -0,0 +1,39 @@
#ifndef _UNSUBSCRIBEPLUGININTERFACE_H_
#define _UNSUBSCRIBEPLUGININTERFACE_H_
#include <MessageViewer/ViewerPluginInterface>
#include "unsubscribemanager.h"
namespace MessageViewer
{
class UnsubscribePluginInterface : public MessageViewer::ViewerPluginInterface
{
Q_OBJECT
public:
explicit UnsubscribePluginInterface(QWidget *parent, KActionCollection *ac = nullptr);
~UnsubscribePluginInterface() override;
[[nodiscard]] QList<QAction *> actions() const override;
void closePlugin() override;
void execute() override;
void setMessageItem(const Akonadi::Item &item) override;
void updateAction(const Akonadi::Item &item) override;
[[nodiscard]] ViewerPluginInterface::SpecificFeatureTypes featureTypes() const override
{
return ViewerPluginInterface::NeedMessage;
}
public slots:
void getOneClickResult(bool isSuccess, const QString &resultString);
private:
UnsubscribeManager mUnsub;
// Whether we're in the middle of a one-click unsubscribe operation
bool mUnsubscribing = true;
QList<QAction *> mActions;
QWidget *mParent;
};
}
#endif