summaryrefslogtreecommitdiff
path: root/buteo-plugins/buteo-sync-plugin-mastodon-notifications
diff options
context:
space:
mode:
Diffstat (limited to 'buteo-plugins/buteo-sync-plugin-mastodon-notifications')
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/buteo-sync-plugin-mastodon-notifications.pro31
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon-notifications.xml3
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon.Notifications.xml3
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.cpp34
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.h1
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.cpp47
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp395
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.h10
8 files changed, 403 insertions, 121 deletions
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/buteo-sync-plugin-mastodon-notifications.pro b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/buteo-sync-plugin-mastodon-notifications.pro
index 81060b1..d16cc3d 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/buteo-sync-plugin-mastodon-notifications.pro
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/buteo-sync-plugin-mastodon-notifications.pro
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2013 - 2026 Jolla Ltd.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
TARGET = mastodon-notifications-client
QT -= gui
@@ -5,6 +9,31 @@ QT -= gui
include($$PWD/../buteo-common/buteo-common.pri)
include($$PWD/../../common/common.pri)
+TS_FILE = $$OUT_PWD/lipstick-jolla-home-mastodon-notifications.ts
+EE_QM = $$OUT_PWD/lipstick-jolla-home-mastodon-notifications_eng_en.qm
+
+ts.commands += lupdate $$PWD -ts $$TS_FILE
+ts.CONFIG += no_check_exist no_link
+ts.output = $$TS_FILE
+ts.input = .
+
+ts_install.files = $$TS_FILE
+ts_install.path = /usr/share/translations/source
+ts_install.CONFIG += no_check_exist
+
+engineering_english.commands += lrelease -idbased $$TS_FILE -qm $$EE_QM
+engineering_english.CONFIG += no_check_exist no_link
+engineering_english.depends = ts
+engineering_english.input = $$TS_FILE
+engineering_english.output = $$EE_QM
+
+engineering_english_install.path = /usr/share/translations
+engineering_english_install.files = $$EE_QM
+engineering_english_install.CONFIG += no_check_exist
+
+QMAKE_EXTRA_TARGETS += ts engineering_english
+PRE_TARGETDEPS += ts engineering_english
+
CONFIG += link_pkgconfig
PKGCONFIG += mlite5 nemonotifications-qt5
@@ -34,4 +63,4 @@ sync.files = mastodon.Notifications.xml
client.path = /etc/buteo/profiles/client
client.files = mastodon-notifications.xml
-INSTALLS += target sync client
+INSTALLS += target sync client ts_install engineering_english_install
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon-notifications.xml b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon-notifications.xml
index 8d042d9..3284d61 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon-notifications.xml
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon-notifications.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2013-2026 Jolla Ltd. -->
+<!-- SPDX-FileCopyrightText: 2013 - 2026 Jolla Ltd. -->
+<!-- SPDX-License-Identifier: BSD-3-Clause -->
<profile name="mastodon-notifications" type="client" >
<field name="Sync Direction" />
</profile>
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon.Notifications.xml b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon.Notifications.xml
index 2843180..05d5218 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon.Notifications.xml
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon.Notifications.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2013-2026 Jolla Ltd. -->
+<!-- SPDX-FileCopyrightText: 2013 - 2026 Jolla Ltd. -->
+<!-- SPDX-License-Identifier: BSD-3-Clause -->
<profile name="mastodon.Notifications" type="sync" >
<key name="category" value="eventfeed" />
<key name="enabled" value="true" />
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.cpp b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.cpp
index 295d7b9..ddf6686 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.cpp
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.cpp
@@ -20,8 +20,8 @@
#include "mastodondatatypesyncadaptor.h"
#include "mastodonauthutils.h"
-#include "trace.h"
+#include <QtCore/QLoggingCategory>
#include <QtCore/QVariantMap>
#include <QtNetwork/QNetworkRequest>
@@ -36,6 +36,8 @@
#include <SignOn/AuthSession>
#include <SignOn/SessionData>
+Q_LOGGING_CATEGORY(lcMastodonNotificationsSync, "buteo.plugin.mastodon.notifications.sync", QtWarningMsg)
+
MastodonNotificationsDataTypeSyncAdaptor::MastodonNotificationsDataTypeSyncAdaptor(
SocialNetworkSyncAdaptor::DataType dataType,
QObject *parent)
@@ -50,7 +52,7 @@ MastodonNotificationsDataTypeSyncAdaptor::~MastodonNotificationsDataTypeSyncAdap
void MastodonNotificationsDataTypeSyncAdaptor::sync(const QString &dataTypeString, int accountId)
{
if (dataTypeString != SocialNetworkSyncAdaptor::dataTypeName(m_dataType)) {
- qCWarning(lcSocialPlugin) << "Mastodon" << SocialNetworkSyncAdaptor::dataTypeName(m_dataType)
+ qCWarning(lcMastodonNotificationsSync) << "Mastodon" << SocialNetworkSyncAdaptor::dataTypeName(m_dataType)
<< "sync adaptor was asked to sync" << dataTypeString;
setStatus(SocialNetworkSyncAdaptor::Error);
return;
@@ -58,14 +60,14 @@ void MastodonNotificationsDataTypeSyncAdaptor::sync(const QString &dataTypeStrin
setStatus(SocialNetworkSyncAdaptor::Busy);
updateDataForAccount(accountId);
- qCDebug(lcSocialPlugin) << "successfully triggered sync with profile:" << m_accountSyncProfile->name();
+ qCDebug(lcMastodonNotificationsSync) << "successfully triggered sync with profile:" << m_accountSyncProfile->name();
}
void MastodonNotificationsDataTypeSyncAdaptor::updateDataForAccount(int accountId)
{
Accounts::Account *account = Accounts::Account::fromId(m_accountManager, accountId, this);
if (!account) {
- qCWarning(lcSocialPlugin) << "existing account with id" << accountId << "couldn't be retrieved";
+ qCWarning(lcMastodonNotificationsSync) << "existing account with id" << accountId << "couldn't be retrieved";
setStatus(SocialNetworkSyncAdaptor::Error);
return;
}
@@ -79,6 +81,11 @@ QString MastodonNotificationsDataTypeSyncAdaptor::apiHost(int accountId) const
return m_apiHosts.value(accountId, QStringLiteral("https://mastodon.social"));
}
+QString MastodonNotificationsDataTypeSyncAdaptor::authServiceName() const
+{
+ return syncServiceName();
+}
+
void MastodonNotificationsDataTypeSyncAdaptor::errorHandler(QNetworkReply::NetworkError err)
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
@@ -89,7 +96,7 @@ void MastodonNotificationsDataTypeSyncAdaptor::errorHandler(QNetworkReply::Netwo
const int accountId = reply->property("accountId").toInt();
const int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- qCWarning(lcSocialPlugin) << SocialNetworkSyncAdaptor::dataTypeName(m_dataType)
+ qCWarning(lcMastodonNotificationsSync) << SocialNetworkSyncAdaptor::dataTypeName(m_dataType)
<< "request with account" << accountId
<< "experienced error:" << err
<< "HTTP:" << httpStatus;
@@ -114,7 +121,7 @@ void MastodonNotificationsDataTypeSyncAdaptor::sslErrorsHandler(const QList<QSsl
sslerrs.chop(2);
}
- qCWarning(lcSocialPlugin) << SocialNetworkSyncAdaptor::dataTypeName(m_dataType)
+ qCWarning(lcMastodonNotificationsSync) << SocialNetworkSyncAdaptor::dataTypeName(m_dataType)
<< "request with account" << sender()->property("accountId").toInt()
<< "experienced ssl errors:" << sslerrs;
sender()->setProperty("isError", QVariant::fromValue<bool>(true));
@@ -122,8 +129,8 @@ void MastodonNotificationsDataTypeSyncAdaptor::sslErrorsHandler(const QList<QSsl
void MastodonNotificationsDataTypeSyncAdaptor::setCredentialsNeedUpdate(Accounts::Account *account)
{
- qCInfo(lcSocialPlugin) << "sociald:Mastodon: setting CredentialsNeedUpdate to true for account:" << account->id();
- Accounts::Service srv(m_accountManager->service(syncServiceName()));
+ qCInfo(lcMastodonNotificationsSync) << "sociald:Mastodon: setting CredentialsNeedUpdate to true for account:" << account->id();
+ Accounts::Service srv(m_accountManager->service(authServiceName()));
account->selectService(srv);
account->setValue(QStringLiteral("CredentialsNeedUpdate"), QVariant::fromValue<bool>(true));
account->setValue(QStringLiteral("CredentialsNeedUpdateFrom"), QVariant::fromValue<QString>(QString::fromLatin1("sociald-mastodon")));
@@ -139,13 +146,14 @@ void MastodonNotificationsDataTypeSyncAdaptor::signIn(Accounts::Account *account
return;
}
- Accounts::Service srv(m_accountManager->service(syncServiceName()));
+ Accounts::Service srv(m_accountManager->service(authServiceName()));
account->selectService(srv);
+
SignOn::Identity *identity = account->credentialsId() > 0
? SignOn::Identity::existingIdentity(account->credentialsId())
: 0;
if (!identity) {
- qCWarning(lcSocialPlugin) << "account" << accountId << "has no valid credentials, cannot sign in";
+ qCWarning(lcMastodonNotificationsSync) << "account" << accountId << "has no valid credentials, cannot sign in";
decrementSemaphore(accountId);
return;
}
@@ -155,7 +163,7 @@ void MastodonNotificationsDataTypeSyncAdaptor::signIn(Accounts::Account *account
const QString mechanism = accSrv.authData().mechanism();
SignOn::AuthSession *session = identity->createSession(method);
if (!session) {
- qCWarning(lcSocialPlugin) << "could not create signon session for account" << accountId;
+ qCWarning(lcMastodonNotificationsSync) << "could not create signon session for account" << accountId;
identity->deleteLater();
decrementSemaphore(accountId);
return;
@@ -183,7 +191,7 @@ void MastodonNotificationsDataTypeSyncAdaptor::signOnError(const SignOn::Error &
SignOn::Identity *identity = session->property("identity").value<SignOn::Identity*>();
const int accountId = account->id();
- qCWarning(lcSocialPlugin) << "credentials for account with id" << accountId
+ qCWarning(lcMastodonNotificationsSync) << "credentials for account with id" << accountId
<< "couldn't be retrieved:" << error.type() << error.message();
if (error.type() == SignOn::Error::UserInteraction) {
@@ -211,7 +219,7 @@ void MastodonNotificationsDataTypeSyncAdaptor::signOnResponse(const SignOn::Sess
accessToken = MastodonAuthUtils::accessToken(data);
if (accessToken.isEmpty()) {
- qCWarning(lcSocialPlugin) << "signon response for account with id" << accountId
+ qCWarning(lcMastodonNotificationsSync) << "signon response for account with id" << accountId
<< "contained no access token; keys:" << data.keys();
}
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.h b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.h
index 3bb6e23..3c61ade 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.h
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.h
@@ -48,6 +48,7 @@ public:
protected:
QString apiHost(int accountId) const;
virtual void updateDataForAccount(int accountId);
+ virtual QString authServiceName() const;
virtual void beginSync(int accountId, const QString &accessToken) = 0;
protected Q_SLOTS:
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.cpp b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.cpp
index fe9f989..9dd3724 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.cpp
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.cpp
@@ -22,6 +22,52 @@
#include "mastodonnotificationssyncadaptor.h"
#include "socialnetworksyncadaptor.h"
+#include <QCoreApplication>
+#include <QLocale>
+#include <QTranslator>
+
+namespace {
+class AppTranslator : public QTranslator
+{
+public:
+ explicit AppTranslator(QObject *parent)
+ : QTranslator(parent)
+ {
+ qApp->installTranslator(this);
+ }
+
+ ~AppTranslator() override
+ {
+ qApp->removeTranslator(this);
+ }
+};
+
+void ensureNotificationTranslations()
+{
+ static bool initialized = false;
+ if (initialized) {
+ return;
+ }
+
+ QCoreApplication *app = QCoreApplication::instance();
+ if (!app) {
+ return;
+ }
+
+ AppTranslator *engineeringEnglish = new AppTranslator(app);
+ engineeringEnglish->load(QStringLiteral("lipstick-jolla-home-mastodon-notifications_eng_en"),
+ QStringLiteral("/usr/share/translations"));
+
+ AppTranslator *translator = new AppTranslator(app);
+ translator->load(QLocale(),
+ QStringLiteral("lipstick-jolla-home-mastodon-notifications"),
+ QStringLiteral("-"),
+ QStringLiteral("/usr/share/translations"));
+
+ initialized = true;
+}
+}
+
MastodonNotificationsPlugin::MastodonNotificationsPlugin(const QString& pluginName,
const Buteo::SyncProfile& profile,
Buteo::PluginCbInterface *callbackInterface)
@@ -29,6 +75,7 @@ MastodonNotificationsPlugin::MastodonNotificationsPlugin(const QString& pluginNa
QStringLiteral("mastodon"),
SocialNetworkSyncAdaptor::dataTypeName(SocialNetworkSyncAdaptor::Notifications))
{
+ ensureNotificationTranslations();
}
MastodonNotificationsPlugin::~MastodonNotificationsPlugin()
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp
index 7775841..98dbbc8 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp
@@ -19,12 +19,13 @@
****************************************************************************/
#include "mastodonnotificationssyncadaptor.h"
-#include "trace.h"
+#include "mastodontextutils.h"
+#include <QtCore/QCoreApplication>
+#include <QtCore/QLoggingCategory>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValue>
-#include <QtCore/QRegularExpression>
#include <QtCore/QUrl>
#include <QtCore/QUrlQuery>
#include <QtNetwork/QNetworkRequest>
@@ -50,42 +51,79 @@
)
namespace {
+ Q_LOGGING_CATEGORY(lcMastodonNotifications, "buteo.plugin.mastodon.notifications", QtWarningMsg)
+
const char *const NotificationCategory = "x-nemo.social.mastodon.notification";
const char *const NotificationIdHint = "x-nemo.sociald.notification-id";
const char *const LastFetchedNotificationIdKey = "LastFetchedNotificationId";
const int NotificationsPageLimit = 80;
-
- QString decodeHtmlEntities(QString text)
- {
- text.replace(QStringLiteral("&quot;"), QStringLiteral("\""));
- text.replace(QStringLiteral("&apos;"), QStringLiteral("'"));
- text.replace(QStringLiteral("&lt;"), QStringLiteral("<"));
- text.replace(QStringLiteral("&gt;"), QStringLiteral(">"));
- text.replace(QStringLiteral("&amp;"), QStringLiteral("&"));
- text.replace(QStringLiteral("&nbsp;"), QStringLiteral(" "));
-
- static const QRegularExpression decimalEntity(QStringLiteral("&#(\\d+);"));
- QRegularExpressionMatch match;
- int index = 0;
- while ((index = text.indexOf(decimalEntity, index, &match)) != -1) {
- const uint value = match.captured(1).toUInt();
- const QString replacement = value > 0 ? QString(QChar(value)) : QString();
- text.replace(index, match.capturedLength(0), replacement);
- index += replacement.size();
- }
-
- static const QRegularExpression hexEntity(QStringLiteral("&#x([0-9a-fA-F]+);"));
- index = 0;
- while ((index = text.indexOf(hexEntity, index, &match)) != -1) {
- bool ok = false;
- const uint value = match.captured(1).toUInt(&ok, 16);
- const QString replacement = ok && value > 0 ? QString(QChar(value)) : QString();
- text.replace(index, match.capturedLength(0), replacement);
- index += replacement.size();
- }
-
- return text;
- }
+ const uint NotificationDismissedReason = 1;
+
+ //% "mentioned you"
+ const char *const TrIdMentionedYou = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-mentioned_you");
+ //% "boosted your post"
+ const char *const TrIdBoostedYourPost = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-boosted_your_post");
+ //% "favourited your post"
+ const char *const TrIdFavouritedYourPost = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-favourited_your_post");
+ //% "started following you"
+ const char *const TrIdStartedFollowingYou = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-started_following_you");
+ //% "requested to follow you"
+ const char *const TrIdRequestedToFollowYou = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-requested_to_follow_you");
+ //% "interacted with your poll"
+ const char *const TrIdInteractedWithYourPoll = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-interacted_with_your_poll");
+ //% "posted"
+ const char *const TrIdPosted = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-posted");
+ //% "updated a post"
+ const char *const TrIdUpdatedPost = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-updated_post");
+ //% "signed up"
+ const char *const TrIdSignedUp = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-signed_up");
+ //% "reported an account"
+ const char *const TrIdReportedAccount = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-reported_account");
+ //% "received a moderation warning"
+ const char *const TrIdReceivedModerationWarning = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-received_moderation_warning");
+ //% "quoted your post"
+ const char *const TrIdQuotedYourPost = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-quoted_your_post");
+ //% "updated a post that quoted you"
+ const char *const TrIdUpdatedQuotedPost = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-updated_quoted_post");
+ //% "sent you a notification"
+ const char *const TrIdSentNotification = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-sent_notification");
+
+ //% "An admin blocked an instance"
+ const char *const TrIdAdminBlockedInstance = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-admin_blocked_instance");
+ //% "An admin blocked %1"
+ const char *const TrIdAdminBlockedTarget = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-admin_blocked_target");
+ //% "You blocked an instance"
+ const char *const TrIdYouBlockedInstance = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-you_blocked_instance");
+ //% "You blocked %1"
+ const char *const TrIdYouBlockedTarget = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-you_blocked_target");
+ //% "An account was suspended"
+ const char *const TrIdAccountSuspended = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-account_suspended");
+ //% "%1 was suspended"
+ const char *const TrIdTargetSuspended = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-target_suspended");
+ //% "Some follow relationships were severed"
+ const char *const TrIdRelationshipsSevered = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-relationships_severed");
+ //% "%1 (%2 followers, %3 following removed)"
+ const char *const TrIdRelationshipsSummary = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-relationships_summary");
+
+ //% "A moderator sent you a warning"
+ const char *const TrIdModeratorWarningNone = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-moderator_warning_none");
+ //% "A moderator disabled your account"
+ const char *const TrIdModeratorWarningDisable = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-moderator_warning_disable");
+ //% "A moderator marked specific posts as sensitive"
+ const char *const TrIdModeratorWarningSpecificSensitive = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-moderator_warning_specific_sensitive");
+ //% "A moderator deleted specific posts"
+ const char *const TrIdModeratorWarningDeletePosts = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-moderator_warning_delete_posts");
+ //% "A moderator marked all your posts as sensitive"
+ const char *const TrIdModeratorWarningAllSensitive = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-moderator_warning_all_sensitive");
+ //% "A moderator limited your account"
+ const char *const TrIdModeratorWarningSilence = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-moderator_warning_silence");
+ //% "A moderator suspended your account"
+ const char *const TrIdModeratorWarningSuspend = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-moderator_warning_suspend");
+
+ //% "Mastodon"
+ const char *const TrIdMastodon = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-mastodon");
+ //% "New notification"
+ const char *const TrIdNewNotification = QT_TRID_NOOP("lipstick-jolla-home-la-mastodon-notification-new_notification");
QString displayNameForAccount(const QJsonObject &account)
{
@@ -105,33 +143,112 @@ namespace {
QString actionText(const QString &type)
{
if (type == QLatin1String("mention")) {
- return QStringLiteral("mentioned you");
+ return qtTrId(TrIdMentionedYou);
} else if (type == QLatin1String("reblog")) {
- return QStringLiteral("boosted your post");
+ return qtTrId(TrIdBoostedYourPost);
} else if (type == QLatin1String("favourite")) {
- return QStringLiteral("favourited your post");
+ return qtTrId(TrIdFavouritedYourPost);
} else if (type == QLatin1String("follow")) {
- return QStringLiteral("started following you");
+ return qtTrId(TrIdStartedFollowingYou);
} else if (type == QLatin1String("follow_request")) {
- return QStringLiteral("requested to follow you");
+ return qtTrId(TrIdRequestedToFollowYou);
} else if (type == QLatin1String("poll")) {
- return QStringLiteral("interacted with your poll");
+ return qtTrId(TrIdInteractedWithYourPoll);
} else if (type == QLatin1String("status")) {
- return QStringLiteral("posted");
+ return qtTrId(TrIdPosted);
} else if (type == QLatin1String("update")) {
- return QStringLiteral("updated a post");
+ return qtTrId(TrIdUpdatedPost);
+ } else if (type == QLatin1String("admin.sign_up")) {
+ return qtTrId(TrIdSignedUp);
+ } else if (type == QLatin1String("admin.report")) {
+ return qtTrId(TrIdReportedAccount);
+ } else if (type == QLatin1String("moderation_warning")) {
+ return qtTrId(TrIdReceivedModerationWarning);
+ } else if (type == QLatin1String("quote")) {
+ return qtTrId(TrIdQuotedYourPost);
+ } else if (type == QLatin1String("quoted_update")) {
+ return qtTrId(TrIdUpdatedQuotedPost);
}
- return QStringLiteral("sent you a notification");
+ return qtTrId(TrIdSentNotification);
+ }
+
+ bool useSystemSummary(const QString &notificationType)
+ {
+ return notificationType == QLatin1String("severed_relationships")
+ || notificationType == QLatin1String("moderation_warning");
}
- bool hasActiveNotificationsForAccount(int accountId)
+ QString severedRelationshipsText(const QJsonObject &eventObject)
+ {
+ const QString eventType = eventObject.value(QStringLiteral("type")).toString();
+ const QString targetName = eventObject.value(QStringLiteral("target_name")).toString().trimmed();
+ const int followersCount = eventObject.value(QStringLiteral("followers_count")).toInt();
+ const int followingCount = eventObject.value(QStringLiteral("following_count")).toInt();
+
+ QString action;
+ if (eventType == QLatin1String("domain_block")) {
+ action = targetName.isEmpty()
+ ? qtTrId(TrIdAdminBlockedInstance)
+ : qtTrId(TrIdAdminBlockedTarget).arg(targetName);
+ } else if (eventType == QLatin1String("user_domain_block")) {
+ action = targetName.isEmpty()
+ ? qtTrId(TrIdYouBlockedInstance)
+ : qtTrId(TrIdYouBlockedTarget).arg(targetName);
+ } else if (eventType == QLatin1String("account_suspension")) {
+ action = targetName.isEmpty()
+ ? qtTrId(TrIdAccountSuspended)
+ : qtTrId(TrIdTargetSuspended).arg(targetName);
+ } else {
+ action = qtTrId(TrIdRelationshipsSevered);
+ }
+
+ const int affectedCount = followersCount + followingCount;
+ if (affectedCount <= 0) {
+ return action;
+ }
+
+ return qtTrId(TrIdRelationshipsSummary)
+ .arg(action)
+ .arg(followersCount)
+ .arg(followingCount);
+ }
+
+ QString moderationWarningText(const QJsonObject &warningObject)
+ {
+ const QString warningText = warningObject.value(QStringLiteral("text")).toString().trimmed();
+ if (!warningText.isEmpty()) {
+ return warningText;
+ }
+
+ const QString action = warningObject.value(QStringLiteral("action")).toString();
+ if (action == QLatin1String("none")) {
+ return qtTrId(TrIdModeratorWarningNone);
+ } else if (action == QLatin1String("disable")) {
+ return qtTrId(TrIdModeratorWarningDisable);
+ } else if (action == QLatin1String("mark_statuses_as_sensitive")) {
+ return qtTrId(TrIdModeratorWarningSpecificSensitive);
+ } else if (action == QLatin1String("delete_statuses")) {
+ return qtTrId(TrIdModeratorWarningDeletePosts);
+ } else if (action == QLatin1String("sensitive")) {
+ return qtTrId(TrIdModeratorWarningAllSensitive);
+ } else if (action == QLatin1String("silence")) {
+ return qtTrId(TrIdModeratorWarningSilence);
+ } else if (action == QLatin1String("suspend")) {
+ return qtTrId(TrIdModeratorWarningSuspend);
+ }
+
+ return QString();
+ }
+
+ bool hasActiveNotificationsForAccount(int accountId, const Notification *ignoredNotification = 0)
{
bool hasActiveNotifications = false;
const QList<QObject *> notifications = Notification::notifications();
foreach (QObject *object, notifications) {
Notification *notification = qobject_cast<Notification *>(object);
if (notification
+ && notification != ignoredNotification
&& notification->category() == QLatin1String(NotificationCategory)
&& notification->hintValue("x-nemo.sociald.account-id").toInt() == accountId) {
hasActiveNotifications = true;
@@ -185,6 +302,11 @@ MastodonNotificationsSyncAdaptor::~MastodonNotificationsSyncAdaptor()
QString MastodonNotificationsSyncAdaptor::syncServiceName() const
{
+ return QStringLiteral("mastodon-notifications");
+}
+
+QString MastodonNotificationsSyncAdaptor::authServiceName() const
+{
return QStringLiteral("mastodon-microblog");
}
@@ -192,6 +314,7 @@ void MastodonNotificationsSyncAdaptor::purgeDataForOldAccount(int oldId, SocialN
{
closeAccountNotifications(oldId);
+ m_accessTokens.remove(oldId);
m_pendingSyncStates.remove(oldId);
m_lastMarkedReadIds.remove(oldId);
saveLastFetchedId(oldId, QString());
@@ -199,6 +322,7 @@ void MastodonNotificationsSyncAdaptor::purgeDataForOldAccount(int oldId, SocialN
void MastodonNotificationsSyncAdaptor::beginSync(int accountId, const QString &accessToken)
{
+ m_accessTokens.insert(accountId, accessToken);
m_pendingSyncStates.remove(accountId);
requestUnreadMarker(accountId, accessToken);
}
@@ -206,7 +330,7 @@ void MastodonNotificationsSyncAdaptor::beginSync(int accountId, const QString &a
void MastodonNotificationsSyncAdaptor::finalize(int accountId)
{
if (syncAborted()) {
- qCInfo(lcSocialPlugin) << "sync aborted, won't update notifications";
+ qCInfo(lcMastodonNotifications) << "sync aborted, won't update notifications";
}
Q_UNUSED(accountId)
@@ -214,52 +338,12 @@ void MastodonNotificationsSyncAdaptor::finalize(int accountId)
QString MastodonNotificationsSyncAdaptor::sanitizeContent(const QString &content)
{
- QString plain = content;
- plain.replace(QRegularExpression(QStringLiteral("<\\s*br\\s*/?\\s*>"), QRegularExpression::CaseInsensitiveOption), QStringLiteral("\n"));
- plain.replace(QRegularExpression(QStringLiteral("<\\s*/\\s*p\\s*>"), QRegularExpression::CaseInsensitiveOption), QStringLiteral("\n"));
- plain.remove(QRegularExpression(QStringLiteral("<[^>]+>"), QRegularExpression::CaseInsensitiveOption));
-
- return decodeHtmlEntities(plain).trimmed();
+ return MastodonTextUtils::sanitizeContent(content);
}
QDateTime MastodonNotificationsSyncAdaptor::parseTimestamp(const QString &timestampString)
{
- QDateTime timestamp;
-
-#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
- timestamp = QDateTime::fromString(timestampString, Qt::ISODateWithMs);
- if (timestamp.isValid()) {
- return timestamp;
- }
-#endif
-
- timestamp = QDateTime::fromString(timestampString, Qt::ISODate);
- if (timestamp.isValid()) {
- return timestamp;
- }
-
- const int timeSeparator = timestampString.indexOf(QLatin1Char('T'));
- const int fractionSeparator = timestampString.indexOf(QLatin1Char('.'), timeSeparator + 1);
- if (timeSeparator > -1 && fractionSeparator > -1) {
- int timezoneSeparator = timestampString.indexOf(QLatin1Char('Z'), fractionSeparator + 1);
- if (timezoneSeparator == -1) {
- timezoneSeparator = timestampString.indexOf(QLatin1Char('+'), fractionSeparator + 1);
- }
- if (timezoneSeparator == -1) {
- timezoneSeparator = timestampString.indexOf(QLatin1Char('-'), fractionSeparator + 1);
- }
-
- QString stripped = timestampString;
- if (timezoneSeparator > -1) {
- stripped.remove(fractionSeparator, timezoneSeparator - fractionSeparator);
- } else {
- stripped.truncate(fractionSeparator);
- }
-
- timestamp = QDateTime::fromString(stripped, Qt::ISODate);
- }
-
- return timestamp;
+ return MastodonTextUtils::parseTimestamp(timestampString);
}
int MastodonNotificationsSyncAdaptor::compareNotificationIds(const QString &left, const QString &right)
@@ -342,7 +426,7 @@ void MastodonNotificationsSyncAdaptor::requestUnreadMarker(int accountId, const
incrementSemaphore(accountId);
setupReplyTimeout(accountId, reply);
} else {
- qCWarning(lcSocialPlugin) << "unable to request notifications marker from Mastodon account with id" << accountId;
+ qCWarning(lcMastodonNotifications) << "unable to request notifications marker from Mastodon account with id" << accountId;
}
}
@@ -365,8 +449,13 @@ void MastodonNotificationsSyncAdaptor::finishedUnreadMarkerHandler()
bool ok = false;
const QJsonObject markerObject = parseJsonObjectReplyData(replyData, &ok);
if (isError || !ok) {
- qCWarning(lcSocialPlugin) << "unable to parse notifications marker data from request with account"
+ qCWarning(lcMastodonNotifications) << "unable to parse notifications marker data from request with account"
<< accountId << ", got:" << QString::fromUtf8(replyData);
+ PendingSyncState fallbackState;
+ fallbackState.accessToken = accessToken;
+ fallbackState.lastFetchedId = loadLastFetchedId(accountId);
+ m_pendingSyncStates.insert(accountId, fallbackState);
+ requestNotifications(accountId, accessToken, fallbackState.lastFetchedId);
decrementSemaphore(accountId);
return;
}
@@ -380,6 +469,7 @@ void MastodonNotificationsSyncAdaptor::finishedUnreadMarkerHandler()
PendingSyncState state;
state.accessToken = accessToken;
+ state.markerKnown = true;
state.unreadFloorId = markerId;
state.lastFetchedId = loadLastFetchedId(accountId);
if (state.lastFetchedId.isEmpty() && !markerId.isEmpty()) {
@@ -424,7 +514,7 @@ void MastodonNotificationsSyncAdaptor::requestNotifications(int accountId,
incrementSemaphore(accountId);
setupReplyTimeout(accountId, reply);
} else {
- qCWarning(lcSocialPlugin) << "unable to request notifications from Mastodon account with id" << accountId;
+ qCWarning(lcMastodonNotifications) << "unable to request notifications from Mastodon account with id" << accountId;
}
}
@@ -452,7 +542,7 @@ void MastodonNotificationsSyncAdaptor::requestMarkRead(int accountId,
incrementSemaphore(accountId);
setupReplyTimeout(accountId, reply);
} else {
- qCWarning(lcSocialPlugin) << "unable to update notifications marker for Mastodon account with id" << accountId;
+ qCWarning(lcMastodonNotifications) << "unable to update notifications marker for Mastodon account with id" << accountId;
}
}
@@ -490,7 +580,10 @@ void MastodonNotificationsSyncAdaptor::finishedNotificationsHandler()
const QJsonArray notifications = parseJsonArrayReplyData(replyData, &ok);
if (!isError && ok) {
if (!notifications.size()) {
- qCDebug(lcSocialPlugin) << "no notifications received for account" << accountId;
+ qCDebug(lcMastodonNotifications) << "no notifications received for account" << accountId;
+ if (state.markerKnown) {
+ closeAccountNotifications(accountId);
+ }
m_pendingSyncStates.remove(accountId);
decrementSemaphore(accountId);
return;
@@ -514,6 +607,10 @@ void MastodonNotificationsSyncAdaptor::finishedNotificationsHandler()
pageMinNotificationId = notificationId;
}
+ if (state.markerKnown) {
+ state.unreadNotificationIds.insert(notificationId);
+ }
+
if (state.maxFetchedId.isEmpty()
|| compareNotificationIds(notificationId, state.maxFetchedId) > 0) {
state.maxFetchedId = notificationId;
@@ -530,6 +627,8 @@ void MastodonNotificationsSyncAdaptor::finishedNotificationsHandler()
const QJsonObject statusObject = statusValue.isObject() && !statusValue.isNull()
? statusValue.toObject()
: QJsonObject();
+ const QJsonObject eventObject = notificationObject.value(QStringLiteral("event")).toObject();
+ const QJsonObject warningObject = notificationObject.value(QStringLiteral("moderation_warning")).toObject();
QDateTime eventTimestamp = parseTimestamp(notificationObject.value(QStringLiteral("created_at")).toString());
if (!eventTimestamp.isValid()) {
@@ -545,9 +644,18 @@ void MastodonNotificationsSyncAdaptor::finishedNotificationsHandler()
const QString statusBody = sanitizeContent(statusObject.value(QStringLiteral("content")).toString());
const QString action = actionText(notificationType);
QString body;
- if (notificationType == QLatin1String("mention")
+ if (notificationType == QLatin1String("severed_relationships")) {
+ body = severedRelationshipsText(eventObject);
+ } else if (notificationType == QLatin1String("moderation_warning")) {
+ const QString warningText = moderationWarningText(warningObject);
+ body = warningText.isEmpty()
+ ? action
+ : QStringLiteral("%1: %2").arg(action, warningText);
+ } else if (notificationType == QLatin1String("mention")
|| notificationType == QLatin1String("status")
- || notificationType == QLatin1String("update")) {
+ || notificationType == QLatin1String("update")
+ || notificationType == QLatin1String("quote")
+ || notificationType == QLatin1String("quoted_update")) {
body = statusBody.isEmpty() ? action : statusBody;
} else {
body = statusBody.isEmpty() ? action : QStringLiteral("%1: %2").arg(action, statusBody);
@@ -567,22 +675,30 @@ void MastodonNotificationsSyncAdaptor::finishedNotificationsHandler()
} else if (url.isEmpty() && !accountName.isEmpty()) {
url = QStringLiteral("%1/@%2").arg(apiHost(accountId), accountName);
}
+ if (useSystemSummary(notificationType)) {
+ url.clear();
+ }
PendingNotification pendingNotification;
pendingNotification.notificationId = notificationId;
- pendingNotification.summary = displayName;
+ pendingNotification.summary = useSystemSummary(notificationType)
+ ? qtTrId(TrIdMastodon)
+ : displayName;
pendingNotification.body = body;
pendingNotification.link = url;
pendingNotification.timestamp = eventTimestamp;
state.pendingNotifications.insert(notificationId, pendingNotification);
}
+ const QString historyBoundaryId = !state.unreadFloorId.isEmpty()
+ ? state.unreadFloorId
+ : state.lastFetchedId;
if (notifications.size() >= NotificationsPageLimit
&& !pageMinNotificationId.isEmpty()
- && (state.unreadFloorId.isEmpty()
- || compareNotificationIds(pageMinNotificationId, state.unreadFloorId) > 0)) {
+ && !historyBoundaryId.isEmpty()
+ && compareNotificationIds(pageMinNotificationId, historyBoundaryId) > 0) {
m_pendingSyncStates.insert(accountId, state);
- requestNotifications(accountId, state.accessToken, state.unreadFloorId, pageMinNotificationId);
+ requestNotifications(accountId, state.accessToken, historyBoundaryId, pageMinNotificationId);
decrementSemaphore(accountId);
return;
}
@@ -599,6 +715,10 @@ void MastodonNotificationsSyncAdaptor::finishedNotificationsHandler()
}
}
+ if (state.markerKnown) {
+ closeAccountNotifications(accountId, state.unreadNotificationIds);
+ }
+
if (!state.maxFetchedId.isEmpty()
&& (state.lastFetchedId.isEmpty()
|| compareNotificationIds(state.maxFetchedId, state.lastFetchedId) > 0)) {
@@ -611,13 +731,13 @@ void MastodonNotificationsSyncAdaptor::finishedNotificationsHandler()
const QString currentMarkerId = m_lastMarkedReadIds.value(accountId);
if (!markerId.isEmpty()
&& !state.accessToken.isEmpty()
- && !hasActiveNotificationsForAccount(accountId)
+ && state.markerKnown
&& (currentMarkerId.isEmpty()
|| compareNotificationIds(markerId, currentMarkerId) > 0)) {
- requestMarkRead(accountId, state.accessToken, markerId);
+ maybeMarkAccountNotificationsRead(accountId, state.accessToken);
}
} else {
- qCWarning(lcSocialPlugin) << "unable to parse notifications data from request with account" << accountId
+ qCWarning(lcMastodonNotifications) << "unable to parse notifications data from request with account" << accountId
<< ", got:" << QString::fromUtf8(replyData);
}
@@ -649,7 +769,7 @@ void MastodonNotificationsSyncAdaptor::finishedMarkReadHandler()
m_lastMarkedReadIds.insert(accountId, lastReadId);
}
} else {
- qCWarning(lcSocialPlugin) << "unable to update notifications marker for account" << accountId
+ qCWarning(lcMastodonNotifications) << "unable to update notifications marker for account" << accountId
<< ", got:" << QString::fromUtf8(replyData);
}
@@ -665,10 +785,10 @@ void MastodonNotificationsSyncAdaptor::publishSystemNotification(int accountId,
? notificationData.timestamp
: QDateTime::currentDateTimeUtc());
notification->setSummary(notificationData.summary.isEmpty()
- ? QStringLiteral("Mastodon")
+ ? qtTrId(TrIdMastodon)
: notificationData.summary);
notification->setBody(notificationData.body.isEmpty()
- ? QStringLiteral("New notification")
+ ? qtTrId(TrIdNewNotification)
: notificationData.body);
notification->setPreviewSummary(notificationData.summary);
notification->setPreviewBody(notificationData.body);
@@ -686,11 +806,75 @@ void MastodonNotificationsSyncAdaptor::publishSystemNotification(int accountId,
notification->setRemoteAction(OPEN_URL_ACTION(authorizeInteractionUrl(apiHost(accountId), safeOpenUrl)));
notification->publish();
if (notification->replacesId() == 0) {
- qCWarning(lcSocialPlugin) << "failed to publish Mastodon notification"
+ qCWarning(lcMastodonNotifications) << "failed to publish Mastodon notification"
<< notificationData.notificationId;
}
}
+void MastodonNotificationsSyncAdaptor::notificationClosedWithReason(uint reason)
+{
+ Notification *notification = qobject_cast<Notification *>(sender());
+ removeCachedNotification(notification);
+ if (reason == NotificationDismissedReason) {
+ markReadFromNotification(notification);
+ }
+}
+
+void MastodonNotificationsSyncAdaptor::maybeMarkAccountNotificationsRead(int accountId,
+ const QString &accessToken,
+ Notification *ignoredNotification)
+{
+ if (accountId <= 0 || accessToken.isEmpty()) {
+ return;
+ }
+
+ if (hasActiveNotificationsForAccount(accountId, ignoredNotification)) {
+ return;
+ }
+
+ const QString lastReadId = loadLastFetchedId(accountId);
+ if (lastReadId.isEmpty()) {
+ return;
+ }
+
+ const QString currentMarkerId = m_lastMarkedReadIds.value(accountId);
+ if (!currentMarkerId.isEmpty() && compareNotificationIds(lastReadId, currentMarkerId) <= 0) {
+ return;
+ }
+
+ requestMarkRead(accountId, accessToken, lastReadId);
+}
+
+void MastodonNotificationsSyncAdaptor::markReadFromNotification(Notification *notification)
+{
+ if (!notification) {
+ return;
+ }
+
+ const int accountId = notification->hintValue("x-nemo.sociald.account-id").toInt();
+ const QString accessToken = m_accessTokens.value(accountId).trimmed();
+ if (accountId <= 0 || accessToken.isEmpty()) {
+ return;
+ }
+
+ maybeMarkAccountNotificationsRead(accountId, accessToken, notification);
+}
+
+void MastodonNotificationsSyncAdaptor::removeCachedNotification(Notification *notification)
+{
+ if (!notification) {
+ return;
+ }
+
+ const int accountId = notification->hintValue("x-nemo.sociald.account-id").toInt();
+ const QString notificationId = notification->hintValue(NotificationIdHint).toString();
+ if (accountId <= 0 || notificationId.isEmpty()) {
+ return;
+ }
+
+ m_notificationObjects.remove(notificationObjectKey(accountId, notificationId));
+}
+
void MastodonNotificationsSyncAdaptor::closeAccountNotifications(int accountId,
const QSet<QString> &keepNotificationIds)
{
@@ -755,6 +939,7 @@ Notification *MastodonNotificationsSyncAdaptor::createNotification(int accountId
notification->setHintValue("x-nemo-priority", 100); // Show on lockscreen
notification->setCategory(QLatin1String(NotificationCategory));
+ connect(notification, SIGNAL(closed(uint)), this, SLOT(notificationClosedWithReason(uint)), Qt::UniqueConnection);
m_notificationObjects.insert(objectKey, notification);
return notification;
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.h b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.h
index 9711549..0e9106c 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.h
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.h
@@ -41,6 +41,7 @@ public:
QString syncServiceName() const override;
protected:
+ QString authServiceName() const override;
void purgeDataForOldAccount(int oldId, SocialNetworkSyncAdaptor::PurgeMode mode) override;
void beginSync(int accountId, const QString &accessToken) override;
void finalize(int accountId) override;
@@ -56,9 +57,11 @@ private:
struct PendingSyncState {
QString accessToken;
+ bool markerKnown = false;
QString unreadFloorId;
QString lastFetchedId;
QString maxFetchedId;
+ QSet<QString> unreadNotificationIds;
QHash<QString, PendingNotification> pendingNotifications;
};
@@ -79,13 +82,20 @@ private:
Notification *findNotification(int accountId, const QString &notificationId);
void closeAccountNotifications(int accountId, const QSet<QString> &keepNotificationIds = QSet<QString>());
static QString notificationObjectKey(int accountId, const QString &notificationId);
+ void maybeMarkAccountNotificationsRead(int accountId,
+ const QString &accessToken,
+ Notification *ignoredNotification = 0);
+ void markReadFromNotification(Notification *notification);
+ void removeCachedNotification(Notification *notification);
private Q_SLOTS:
void finishedUnreadMarkerHandler();
void finishedNotificationsHandler();
void finishedMarkReadHandler();
+ void notificationClosedWithReason(uint reason);
private:
+ QHash<int, QString> m_accessTokens;
QHash<int, PendingSyncState> m_pendingSyncStates;
QHash<int, QString> m_lastMarkedReadIds;
QHash<QString, Notification *> m_notificationObjects;