diff options
Diffstat (limited to 'buteo-plugins/buteo-sync-plugin-mastodon-notifications')
4 files changed, 316 insertions, 115 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..3289487 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 @@ -5,6 +5,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 +59,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/mastodondatatypesyncadaptor.cpp b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.cpp index 295d7b9..14b3511 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; } @@ -89,7 +91,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 +116,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,7 +124,7 @@ void MastodonNotificationsDataTypeSyncAdaptor::sslErrorsHandler(const QList<QSsl void MastodonNotificationsDataTypeSyncAdaptor::setCredentialsNeedUpdate(Accounts::Account *account) { - qCInfo(lcSocialPlugin) << "sociald:Mastodon: setting CredentialsNeedUpdate to true for account:" << account->id(); + qCInfo(lcMastodonNotificationsSync) << "sociald:Mastodon: setting CredentialsNeedUpdate to true for account:" << account->id(); Accounts::Service srv(m_accountManager->service(syncServiceName())); account->selectService(srv); account->setValue(QStringLiteral("CredentialsNeedUpdate"), QVariant::fromValue<bool>(true)); @@ -145,7 +147,7 @@ void MastodonNotificationsDataTypeSyncAdaptor::signIn(Accounts::Account *account ? 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 +157,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 +185,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 +213,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/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 d5d1766..8a2c610 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,78 @@ ) 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("""), QStringLiteral("\"")); - text.replace(QStringLiteral("'"), QStringLiteral("'")); - text.replace(QStringLiteral("<"), QStringLiteral("<")); - text.replace(QStringLiteral(">"), QStringLiteral(">")); - text.replace(QStringLiteral("&"), QStringLiteral("&")); - text.replace(QStringLiteral(" "), 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; - } + //% "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,24 +142,102 @@ 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 ¬ificationType) + { + return notificationType == QLatin1String("severed_relationships") + || notificationType == QLatin1String("moderation_warning"); + } + + 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) @@ -143,6 +258,34 @@ namespace { return hasActiveNotifications; } + QString authorizeInteractionUrl(const QString &apiHost, const QString &targetUrl) + { + const QUrl parsedApiHost(apiHost); + const QUrl parsedTargetUrl(targetUrl); + if (!parsedApiHost.isValid() + || parsedApiHost.scheme().isEmpty() + || parsedApiHost.host().isEmpty() + || !parsedTargetUrl.isValid() + || parsedTargetUrl.scheme().isEmpty() + || parsedTargetUrl.host().isEmpty()) { + return targetUrl; + } + + // Links on the account's own instance should open directly. + const bool sameScheme = QString::compare(parsedApiHost.scheme(), parsedTargetUrl.scheme(), Qt::CaseInsensitive) == 0; + const bool sameHost = QString::compare(parsedApiHost.host(), parsedTargetUrl.host(), Qt::CaseInsensitive) == 0; + const int apiPort = parsedApiHost.port(parsedApiHost.scheme() == QLatin1String("https") ? 443 : 80); + const int targetPort = parsedTargetUrl.port(parsedTargetUrl.scheme() == QLatin1String("https") ? 443 : 80); + if (sameScheme && sameHost && apiPort == targetPort) { + return targetUrl; + } + + QUrl authorizeUrl(parsedApiHost); + authorizeUrl.setPath(QStringLiteral("/authorize_interaction")); + authorizeUrl.setQuery(QStringLiteral("uri=") + QString::fromUtf8(QUrl::toPercentEncoding(targetUrl))); + return authorizeUrl.toString(); + } + } MastodonNotificationsSyncAdaptor::MastodonNotificationsSyncAdaptor(QObject *parent) @@ -157,7 +300,7 @@ MastodonNotificationsSyncAdaptor::~MastodonNotificationsSyncAdaptor() QString MastodonNotificationsSyncAdaptor::syncServiceName() const { - return QStringLiteral("mastodon-microblog"); + return QStringLiteral("mastodon-notifications"); } void MastodonNotificationsSyncAdaptor::purgeDataForOldAccount(int oldId, SocialNetworkSyncAdaptor::PurgeMode) @@ -178,7 +321,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) @@ -186,52 +329,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 ×tampString) { - 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) @@ -314,7 +417,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; } } @@ -337,8 +440,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; } @@ -396,7 +504,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; } } @@ -424,7 +532,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; } } @@ -462,7 +570,7 @@ 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; m_pendingSyncStates.remove(accountId); decrementSemaphore(accountId); return; @@ -502,6 +610,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()) { @@ -517,9 +627,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); @@ -539,22 +658,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; } @@ -589,7 +716,7 @@ void MastodonNotificationsSyncAdaptor::finishedNotificationsHandler() requestMarkRead(accountId, state.accessToken, markerId); } } 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); } @@ -621,7 +748,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); } @@ -637,10 +764,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); @@ -655,10 +782,10 @@ void MastodonNotificationsSyncAdaptor::publishSystemNotification(int accountId, && !parsedOpenUrl.host().isEmpty() ? openUrl : fallbackUrl; - notification->setRemoteAction(OPEN_URL_ACTION(safeOpenUrl)); + 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; } } |
