summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Branson <andrew.branson@jolla.com>2026-02-10 17:29:54 +0100
committerAndrew Branson <andrew.branson@jolla.com>2026-02-10 21:13:57 +0100
commit0b9869b23af94f1935839568d62fa7b849f9e5d0 (patch)
tree4485ad8750116e396a3341b874143ca3e88122c3
parent4351f4627ba9e71775438dd26c9acddd002c7e11 (diff)
Notifications
-rw-r--r--README.md69
-rw-r--r--buteo-plugins/buteo-common/buteosyncfw_p.h2
-rw-r--r--buteo-plugins/buteo-common/socialdbuteoplugin.cpp53
-rw-r--r--buteo-plugins/buteo-common/socialdbuteoplugin.h2
-rw-r--r--buteo-plugins/buteo-common/socialdnetworkaccessmanager_p.cpp2
-rw-r--r--buteo-plugins/buteo-common/socialdnetworkaccessmanager_p.h2
-rw-r--r--buteo-plugins/buteo-common/socialnetworksyncadaptor.cpp2
-rw-r--r--buteo-plugins/buteo-common/socialnetworksyncadaptor.h2
-rw-r--r--buteo-plugins/buteo-common/trace.cpp2
-rw-r--r--buteo-plugins/buteo-common/trace.h2
-rw-r--r--buteo-plugins/buteo-plugins.pro4
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/buteo-sync-plugin-mastodon-notifications.pro37
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon-notifications.xml4
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon.Notifications.xml15
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.cpp313
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.h70
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.cpp49
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.h54
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp701
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.h98
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodondatatypesyncadaptor.cpp2
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodondatatypesyncadaptor.h2
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostsplugin.cpp2
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostsplugin.h2
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostssyncadaptor.cpp2
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostssyncadaptor.h2
-rw-r--r--common/common.pro6
-rw-r--r--common/mastodonnotificationsdatabase.cpp87
-rw-r--r--common/mastodonnotificationsdatabase.h46
-rw-r--r--common/mastodonpostsdatabase.cpp2
-rw-r--r--common/mastodonpostsdatabase.h2
-rw-r--r--eventsview-plugins/eventsview-plugin-mastodon/MastodonFeedItem.qml2
-rw-r--r--eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel.cpp2
-rw-r--r--eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel.h2
-rw-r--r--eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel_p.h2
-rw-r--r--eventsview-plugins/eventsview-plugin-mastodon/mastodon-delegate.qml6
-rw-r--r--eventsview-plugins/eventsview-plugin-mastodon/mastodonpostsmodel.cpp2
-rw-r--r--eventsview-plugins/eventsview-plugin-mastodon/mastodonpostsmodel.h2
-rw-r--r--eventsview-plugins/eventsview-plugin-mastodon/postimagehelper_p.h2
-rw-r--r--eventsview-plugins/eventsview-plugin-mastodon/synchronizelists_p.h2
-rw-r--r--rpm/sailfish-account-mastodon.spec42
-rw-r--r--settings/accounts/providers/mastodon.provider2
-rw-r--r--settings/accounts/services/mastodon-microblog.service4
-rw-r--r--settings/accounts/services/mastodon-sharing.service2
-rw-r--r--transferengine-plugins/mastodonshareplugin/mastodonplugininfo.cpp2
-rw-r--r--transferengine-plugins/mastodontransferplugin/mastodonuploader.cpp2
46 files changed, 1607 insertions, 107 deletions
diff --git a/README.md b/README.md
index 6ca09b7..f58b99e 100644
--- a/README.md
+++ b/README.md
@@ -2,70 +2,77 @@
Sailfish OS account integration for Mastodon.
-## What This Repository Contains
-
-This repository is the initial public form of the Mastodon plugin and contains all of its account integration components.
+## Repository Components
### `common/`
- Shared C++ library code used by multiple plugins.
-- Includes Mastodon posts database support built on `socialcache`.
+- Includes socialcache-backed databases for Mastodon posts and notifications.
### `settings/`
-- Sailfish Accounts provider and services definitions.
-- Account UI QML files for account creation/settings/credentials update.
-- Uses OAuth2 (`web_server`) account flow with per-instance Mastodon app registration.
-- Current services:
- - `mastodon-microblog` (sync posts)
- - `mastodon-sharing` (Transfer Engine sharing)
+- Sailfish Accounts provider, service definitions, and account UI.
+- OAuth2 (`web_server`) account flow with per-instance Mastodon app registration.
+- Services:
+ - `mastodon-microblog`: sync service for posts and notifications.
+ - `mastodon-sharing`: Transfer Engine sharing service.
### `buteo-plugins/`
-- Social sync plugins for Buteo.
-- Includes shared Buteo social plugin framework code and Mastodon posts sync plugin/profile files.
-- Installs Buteo client and sync profile XML files.
+- Buteo sync plugins and shared social sync framework code.
+- Includes:
+ - `buteo-sync-plugin-mastodon-posts`
+ - `buteo-sync-plugin-mastodon-notifications`
+- Installs Buteo client profile and sync profile XML files.
### `eventsview-plugins/`
-- Events view extension QML/C++ plugin for Mastodon posts.
+- Events view extension for Mastodon posts.
- Includes delegate/feed item QML and `MastodonPostsModel`.
### `transferengine-plugins/`
- Transfer Engine integration for Mastodon image sharing.
-- `mastodonshareplugin/`: sharing method discovery and share UI metadata.
-- `mastodontransferplugin/`: upload implementation (upload media + create status).
-- Shared account credential/status helper: `mastodonshareservicestatus.*`.
+- `mastodonshareplugin/`: sharing method discovery + metadata.
+- `mastodontransferplugin/`: media upload + status creation.
### `icons/`
-- Mastodon SVG icon assets and Sailfish icon conversion setup (`sailfish-svg2png`).
-- Provides themed service icons used by provider/settings/transfer UI.
-
+- Mastodon SVG assets and `sailfish-svg2png` conversion setup.
+- Uses canonical icon names only:
+ - `icons/icon-l-mastodon`
### `rpm/`
-- RPM spec for packaging all components.
-- Defines subpackages:
+- Packaging for all modules in `rpm/sailfish-account-mastodon.spec`.
+- Subpackages:
- `sailfish-account-mastodon`
- `buteo-sync-plugin-mastodon-posts`
+ - `buteo-sync-plugin-mastodon-notifications`
- `eventsview-extensions-mastodon`
- `transferengine-plugin-mastodon`
- - `sailfish-account-mastodon-features-all`
+- Main package requires all feature subpackages.
+
+### Root project
+- `sailfish-account-mastodon.pro` ties subprojects together.
+
+## Current Notification Behavior
-### Root project file
-- `sailfish-account-mastodon.pro` ties all subprojects together.
+- Events view shows Mastodon posts (not notification entries).
+- System notifications are produced by `buteo-sync-plugin-mastodon-notifications`.
+- Notifications sync fetches unread items using Mastodon markers (`last_read_id`).
+- Dismissing the Sailfish notification marks those items as read on Mastodon via markers API.
+- Notification template profile dispatches per-account sync profiles on schedule (default every 30 minutes), not only at boot.
## Build Requirements
-This project is designed for the Sailfish OS build environment.
+This project targets Sailfish OS build tooling.
-A full build is not possible without Sailfish SDK access (including target sysroot/tooling and Sailfish-specific development packages).
+Full build/package validation is not possible without Sailfish SDK access (target sysroot + Sailfish packages).
-In particular, dependencies like these must come from the Sailfish SDK target environment:
+Required SDK-provided dependencies include (not exhaustive):
- `buteosyncfw5`
- `socialcache`
- `sailfishaccounts`
- `nemotransferengine-qt5`
-- other Sailfish/Qt account stack packages listed in `rpm/sailfish-account-mastodon.spec`
+- related Qt/account stack packages listed in `rpm/sailfish-account-mastodon.spec`
## Typical Build Flow (Inside Sailfish SDK)
1. Enter Sailfish SDK shell/target.
-2. Run qmake build from repository root.
+2. Build from repository root (`qmake` / `make`).
3. Build RPM package(s) from `rpm/sailfish-account-mastodon.spec`.
-If you are outside Sailfish SDK, use static checks and code review only.
+Outside Sailfish SDK, only static validation (wiring, paths, spec consistency) should be considered reliable.
diff --git a/buteo-plugins/buteo-common/buteosyncfw_p.h b/buteo-plugins/buteo-common/buteosyncfw_p.h
index 5924731..9fd6a77 100644
--- a/buteo-plugins/buteo-common/buteosyncfw_p.h
+++ b/buteo-plugins/buteo-common/buteosyncfw_p.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2014 Jolla Ltd.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
** Contact: Chris Adams <chris.adams@jollamobile.com>
**
** This program/library is free software; you can redistribute it and/or
diff --git a/buteo-plugins/buteo-common/socialdbuteoplugin.cpp b/buteo-plugins/buteo-common/socialdbuteoplugin.cpp
index 8b27f84..0eb5f91 100644
--- a/buteo-plugins/buteo-common/socialdbuteoplugin.cpp
+++ b/buteo-plugins/buteo-common/socialdbuteoplugin.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2014 Jolla Ltd.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
** Contact: Chris Adams <chris.adams@jolla.com>
**
** This program/library is free software; you can redistribute it and/or
@@ -149,11 +149,16 @@ bool SocialdButeoPlugin::uninit()
bool SocialdButeoPlugin::startSync()
{
+ if (!m_socialNetworkSyncAdaptor || !m_socialNetworkSyncAdaptor->enabled()) {
+ qCDebug(lcSocialPlugin) << "no enabled" << m_socialServiceName << "sync adaptor for" << m_dataTypeName;
+ return false;
+ }
+
// if the profile being triggered is the template profile, then we
// need to ensure that the appropriate per-account profiles exist.
if (m_profileAccountId == 0) {
QList<Buteo::SyncProfile*> perAccountProfiles = ensurePerAccountSyncProfilesExist();
- m_socialNetworkSyncAdaptor->setAccountSyncProfile(NULL);
+ m_socialNetworkSyncAdaptor->setAccountSyncProfile(nullptr);
// we need to trigger sync with each profile separately,
// or (due to scheduling/etc) another plugin instance might
@@ -165,25 +170,37 @@ bool SocialdButeoPlugin::startSync()
message.setArguments(QVariantList() << perAccountProfile->name());
QDBusConnection::sessionBus().asyncCall(message);
}
- } else {
- m_socialNetworkSyncAdaptor->setAccountSyncProfile(profile().clone());
+ qDeleteAll(perAccountProfiles);
+
+ // This template profile only dispatches account-specific sync profiles.
+ // Those child sync runs report their own individual results.
+ updateResults(Buteo::SyncResults(QDateTime::currentDateTime(),
+ Buteo::SyncResults::SYNC_RESULT_SUCCESS,
+ Buteo::SyncResults::NO_ERROR));
+ emit success(getProfileName(), QString("%1 update dispatched").arg(getProfileName()));
+ return true;
}
- // now perform sync. Note that for the template profile case, this will
- // result in a purge operation occurring (checking for removed accounts and
- // purging any synced data associated with those accounts).
- if (m_socialNetworkSyncAdaptor && m_socialNetworkSyncAdaptor->enabled()) {
- if (m_socialNetworkSyncAdaptor->status() == SocialNetworkSyncAdaptor::Inactive) {
- qCDebug(lcSocialPlugin) << "performing sync of" << m_dataTypeName << "from" << m_socialServiceName
- << "for account" << m_profileAccountId;
- m_socialNetworkSyncAdaptor->sync(m_dataTypeName, m_profileAccountId);
- return true;
- } else {
- qCDebug(lcSocialPlugin) << m_socialServiceName << "sync adaptor for" << m_dataTypeName
- << "is still busy with last sync of account" << m_profileAccountId;
- }
+ Buteo::SyncProfile *accountSyncProfile = profile().clone();
+ if (accountSyncProfile
+ && m_dataTypeName == SocialNetworkSyncAdaptor::dataTypeName(SocialNetworkSyncAdaptor::Notifications)) {
+ accountSyncProfile->setEnabled(true);
+ Buteo::SyncSchedule schedule = accountSyncProfile->syncSchedule();
+ schedule.setScheduleEnabled(true);
+ accountSyncProfile->setSyncSchedule(schedule);
+ m_profileManager.updateProfile(*accountSyncProfile);
+ }
+ m_socialNetworkSyncAdaptor->setAccountSyncProfile(accountSyncProfile);
+
+ // Now perform sync for the account-specific profile.
+ if (m_socialNetworkSyncAdaptor->status() == SocialNetworkSyncAdaptor::Inactive) {
+ qCDebug(lcSocialPlugin) << "performing sync of" << m_dataTypeName << "from" << m_socialServiceName
+ << "for account" << m_profileAccountId;
+ m_socialNetworkSyncAdaptor->sync(m_dataTypeName, m_profileAccountId);
+ return true;
} else {
- qCDebug(lcSocialPlugin) << "no enabled" << m_socialServiceName << "sync adaptor for" << m_dataTypeName;
+ qCDebug(lcSocialPlugin) << m_socialServiceName << "sync adaptor for" << m_dataTypeName
+ << "is still busy with last sync of account" << m_profileAccountId;
}
return false;
}
diff --git a/buteo-plugins/buteo-common/socialdbuteoplugin.h b/buteo-plugins/buteo-common/socialdbuteoplugin.h
index 57de171..2988d27 100644
--- a/buteo-plugins/buteo-common/socialdbuteoplugin.h
+++ b/buteo-plugins/buteo-common/socialdbuteoplugin.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2013-2014 Jolla Ltd.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
** Contact: Raine Makelainen <raine.makelainen@jollamobile.com>
**
** This program/library is free software; you can redistribute it and/or
diff --git a/buteo-plugins/buteo-common/socialdnetworkaccessmanager_p.cpp b/buteo-plugins/buteo-common/socialdnetworkaccessmanager_p.cpp
index 7030ce3..37b4687 100644
--- a/buteo-plugins/buteo-common/socialdnetworkaccessmanager_p.cpp
+++ b/buteo-plugins/buteo-common/socialdnetworkaccessmanager_p.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2013-2014 Jolla Ltd.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
** Contact: Chris Adams <chris.adams@jollamobile.com>
**
** This program/library is free software; you can redistribute it and/or
diff --git a/buteo-plugins/buteo-common/socialdnetworkaccessmanager_p.h b/buteo-plugins/buteo-common/socialdnetworkaccessmanager_p.h
index 846489b..7adde92 100644
--- a/buteo-plugins/buteo-common/socialdnetworkaccessmanager_p.h
+++ b/buteo-plugins/buteo-common/socialdnetworkaccessmanager_p.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2013-2014 Jolla Ltd.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
** Contact: Chris Adams <chris.adams@jollamobile.com>
**
** This program/library is free software; you can redistribute it and/or
diff --git a/buteo-plugins/buteo-common/socialnetworksyncadaptor.cpp b/buteo-plugins/buteo-common/socialnetworksyncadaptor.cpp
index 4451d5f..42e6d95 100644
--- a/buteo-plugins/buteo-common/socialnetworksyncadaptor.cpp
+++ b/buteo-plugins/buteo-common/socialnetworksyncadaptor.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2013-2014 Jolla Ltd.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
** Contact: Chris Adams <chris.adams@jollamobile.com>
**
** This program/library is free software; you can redistribute it and/or
diff --git a/buteo-plugins/buteo-common/socialnetworksyncadaptor.h b/buteo-plugins/buteo-common/socialnetworksyncadaptor.h
index 99adeb3..7c76564 100644
--- a/buteo-plugins/buteo-common/socialnetworksyncadaptor.h
+++ b/buteo-plugins/buteo-common/socialnetworksyncadaptor.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2013-2014 Jolla Ltd.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
** Contact: Chris Adams <chris.adams@jollamobile.com>
**
** This program/library is free software; you can redistribute it and/or
diff --git a/buteo-plugins/buteo-common/trace.cpp b/buteo-plugins/buteo-common/trace.cpp
index ffcdf5b..05e3508 100644
--- a/buteo-plugins/buteo-common/trace.cpp
+++ b/buteo-plugins/buteo-common/trace.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2021 Jolla Ltd.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
**
** This program/library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public License
diff --git a/buteo-plugins/buteo-common/trace.h b/buteo-plugins/buteo-common/trace.h
index b0aeeeb..2b9c1ae 100644
--- a/buteo-plugins/buteo-common/trace.h
+++ b/buteo-plugins/buteo-common/trace.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2013-2014 Jolla Ltd.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
** Contact: Chris Adams <chris.adams@jollamobile.com>
**
** This program/library is free software; you can redistribute it and/or
diff --git a/buteo-plugins/buteo-plugins.pro b/buteo-plugins/buteo-plugins.pro
index 5a48850..179aebf 100644
--- a/buteo-plugins/buteo-plugins.pro
+++ b/buteo-plugins/buteo-plugins.pro
@@ -1,6 +1,8 @@
TEMPLATE = subdirs
SUBDIRS += \
buteo-common \
- buteo-sync-plugin-mastodon-posts
+ buteo-sync-plugin-mastodon-posts \
+ buteo-sync-plugin-mastodon-notifications
buteo-sync-plugin-mastodon-posts.depends = buteo-common
+buteo-sync-plugin-mastodon-notifications.depends = buteo-common
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
new file mode 100644
index 0000000..81060b1
--- /dev/null
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/buteo-sync-plugin-mastodon-notifications.pro
@@ -0,0 +1,37 @@
+TARGET = mastodon-notifications-client
+
+QT -= gui
+
+include($$PWD/../buteo-common/buteo-common.pri)
+include($$PWD/../../common/common.pri)
+
+CONFIG += link_pkgconfig
+PKGCONFIG += mlite5 nemonotifications-qt5
+
+INCLUDEPATH += $$PWD
+
+SOURCES += \
+ $$PWD/mastodondatatypesyncadaptor.cpp \
+ $$PWD/mastodonnotificationsplugin.cpp \
+ $$PWD/mastodonnotificationssyncadaptor.cpp
+
+HEADERS += \
+ $$PWD/mastodondatatypesyncadaptor.h \
+ $$PWD/mastodonnotificationsplugin.h \
+ $$PWD/mastodonnotificationssyncadaptor.h
+
+OTHER_FILES += \
+ $$PWD/mastodon-notifications.xml \
+ $$PWD/mastodon.Notifications.xml
+
+TEMPLATE = lib
+CONFIG += plugin
+target.path = $$[QT_INSTALL_LIBS]/buteo-plugins-qt5/oopp
+
+sync.path = /etc/buteo/profiles/sync
+sync.files = mastodon.Notifications.xml
+
+client.path = /etc/buteo/profiles/client
+client.files = mastodon-notifications.xml
+
+INSTALLS += target sync client
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon-notifications.xml b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon-notifications.xml
new file mode 100644
index 0000000..6648a44
--- /dev/null
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon-notifications.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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
new file mode 100644
index 0000000..85f24e6
--- /dev/null
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodon.Notifications.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<profile name="mastodon.Notifications" type="sync" >
+ <key name="category" value="eventfeed" />
+ <key name="enabled" value="true" />
+ <key name="use_accounts" value="false" />
+ <key name="destinationtype" value="online" />
+ <key name="hidden" value="true" />
+ <key name="displayname" value="Mastodon Notifications"/>
+
+ <schedule enabled="true" interval="30" days="1,2,3,4,5,6,7" syncconfiguredtime="" time="" />
+
+ <profile name="mastodon-notifications" type="client" >
+ <key name="Sync Direction" value="from-remote" />
+ </profile>
+</profile>
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.cpp b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.cpp
new file mode 100644
index 0000000..f915507
--- /dev/null
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.cpp
@@ -0,0 +1,313 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2013-2026 Jolla Ltd.
+ **
+ ** This program/library is free software; you can redistribute it and/or
+ ** modify it under the terms of the GNU Lesser General Public License
+ ** version 2.1 as published by the Free Software Foundation.
+ **
+ ** This program/library is distributed in the hope that it will be useful,
+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ ** Lesser General Public License for more details.
+ **
+ ** You should have received a copy of the GNU Lesser General Public
+ ** License along with this program/library; if not, write to the Free
+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ ** 02110-1301 USA
+ **
+ ****************************************************************************/
+
+#include "mastodondatatypesyncadaptor.h"
+#include "trace.h"
+
+#include <QtCore/QVariantMap>
+#include <QtCore/QUrl>
+#include <QtNetwork/QNetworkRequest>
+
+// libaccounts-qt5
+#include <Accounts/Manager>
+#include <Accounts/Account>
+#include <Accounts/Service>
+#include <Accounts/AccountService>
+
+// libsignon-qt5
+#include <SignOn/Identity>
+#include <SignOn/AuthSession>
+#include <SignOn/SessionData>
+
+MastodonNotificationsDataTypeSyncAdaptor::MastodonNotificationsDataTypeSyncAdaptor(
+ SocialNetworkSyncAdaptor::DataType dataType,
+ QObject *parent)
+ : SocialNetworkSyncAdaptor(QStringLiteral("mastodon"), dataType, 0, parent)
+{
+}
+
+MastodonNotificationsDataTypeSyncAdaptor::~MastodonNotificationsDataTypeSyncAdaptor()
+{
+}
+
+void MastodonNotificationsDataTypeSyncAdaptor::sync(const QString &dataTypeString, int accountId)
+{
+ if (dataTypeString != SocialNetworkSyncAdaptor::dataTypeName(m_dataType)) {
+ qCWarning(lcSocialPlugin) << "Mastodon" << SocialNetworkSyncAdaptor::dataTypeName(m_dataType)
+ << "sync adaptor was asked to sync" << dataTypeString;
+ setStatus(SocialNetworkSyncAdaptor::Error);
+ return;
+ }
+
+ setStatus(SocialNetworkSyncAdaptor::Busy);
+ updateDataForAccount(accountId);
+ qCDebug(lcSocialPlugin) << "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";
+ setStatus(SocialNetworkSyncAdaptor::Error);
+ return;
+ }
+
+ incrementSemaphore(accountId);
+ signIn(account);
+}
+
+QString MastodonNotificationsDataTypeSyncAdaptor::apiHost(int accountId) const
+{
+ return m_apiHosts.value(accountId, QStringLiteral("https://mastodon.social"));
+}
+
+void MastodonNotificationsDataTypeSyncAdaptor::errorHandler(QNetworkReply::NetworkError err)
+{
+ QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
+ if (!reply) {
+ return;
+ }
+
+ const int accountId = reply->property("accountId").toInt();
+ const int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+ qCWarning(lcSocialPlugin) << SocialNetworkSyncAdaptor::dataTypeName(m_dataType)
+ << "request with account" << accountId
+ << "experienced error:" << err
+ << "HTTP:" << httpStatus;
+
+ reply->setProperty("isError", QVariant::fromValue<bool>(true));
+
+ if (httpStatus == 401 || err == QNetworkReply::AuthenticationRequiredError) {
+ Accounts::Account *account = Accounts::Account::fromId(m_accountManager, accountId, this);
+ if (account) {
+ setCredentialsNeedUpdate(account);
+ }
+ }
+}
+
+void MastodonNotificationsDataTypeSyncAdaptor::sslErrorsHandler(const QList<QSslError> &errs)
+{
+ QString sslerrs;
+ foreach (const QSslError &e, errs) {
+ sslerrs += e.errorString() + QLatin1String("; ");
+ }
+ if (!sslerrs.isEmpty()) {
+ sslerrs.chop(2);
+ }
+
+ qCWarning(lcSocialPlugin) << SocialNetworkSyncAdaptor::dataTypeName(m_dataType)
+ << "request with account" << sender()->property("accountId").toInt()
+ << "experienced ssl errors:" << sslerrs;
+ sender()->setProperty("isError", QVariant::fromValue<bool>(true));
+}
+
+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()));
+ account->selectService(srv);
+ account->setValue(QStringLiteral("CredentialsNeedUpdate"), QVariant::fromValue<bool>(true));
+ account->setValue(QStringLiteral("CredentialsNeedUpdateFrom"), QVariant::fromValue<QString>(QString::fromLatin1("sociald-mastodon")));
+ account->selectService(Accounts::Service());
+ account->syncAndBlock();
+}
+
+QString MastodonNotificationsDataTypeSyncAdaptor::normalizeApiHost(const QString &rawHost)
+{
+ QString host = rawHost.trimmed();
+ if (host.isEmpty()) {
+ host = QStringLiteral("https://mastodon.social");
+ }
+ if (!host.startsWith(QLatin1String("https://"))
+ && !host.startsWith(QLatin1String("http://"))) {
+ host.prepend(QStringLiteral("https://"));
+ }
+
+ QUrl url(host);
+ if (!url.isValid() || url.host().isEmpty()) {
+ return QStringLiteral("https://mastodon.social");
+ }
+
+ QString normalized = QString::fromLatin1(url.toEncoded(QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment));
+ if (normalized.endsWith(QLatin1Char('/'))) {
+ normalized.chop(1);
+ }
+ return normalized;
+}
+
+void MastodonNotificationsDataTypeSyncAdaptor::signIn(Accounts::Account *account)
+{
+ const int accountId = account->id();
+ if (!checkAccount(account)) {
+ decrementSemaphore(accountId);
+ return;
+ }
+
+ Accounts::Service srv(m_accountManager->service(syncServiceName()));
+ 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";
+ decrementSemaphore(accountId);
+ return;
+ }
+
+ Accounts::AccountService accSrv(account, srv);
+ const QString method = accSrv.authData().method();
+ const QString mechanism = accSrv.authData().mechanism();
+ SignOn::AuthSession *session = identity->createSession(method);
+ if (!session) {
+ qCWarning(lcSocialPlugin) << "could not create signon session for account" << accountId;
+ identity->deleteLater();
+ decrementSemaphore(accountId);
+ return;
+ }
+
+ QVariantMap signonSessionData = accSrv.authData().parameters();
+ QString configuredHost = account->value(QStringLiteral("auth/oauth2/web_server/Host")).toString().trimmed();
+ if (configuredHost.isEmpty()) {
+ configuredHost = normalizeApiHost(account->value(QStringLiteral("api/Host")).toString());
+ }
+ if (configuredHost.startsWith(QLatin1String("https://"))) {
+ configuredHost.remove(0, 8);
+ } else if (configuredHost.startsWith(QLatin1String("http://"))) {
+ configuredHost.remove(0, 7);
+ }
+ while (configuredHost.endsWith(QLatin1Char('/'))) {
+ configuredHost.chop(1);
+ }
+ if (configuredHost.isEmpty()) {
+ configuredHost = QStringLiteral("mastodon.social");
+ }
+ signonSessionData.insert(QStringLiteral("Host"), configuredHost);
+
+ const QString authPath = account->value(QStringLiteral("auth/oauth2/web_server/AuthPath")).toString().trimmed();
+ if (!authPath.isEmpty()) {
+ signonSessionData.insert(QStringLiteral("AuthPath"), authPath);
+ }
+
+ const QString tokenPath = account->value(QStringLiteral("auth/oauth2/web_server/TokenPath")).toString().trimmed();
+ if (!tokenPath.isEmpty()) {
+ signonSessionData.insert(QStringLiteral("TokenPath"), tokenPath);
+ }
+
+ const QString responseType = account->value(QStringLiteral("auth/oauth2/web_server/ResponseType")).toString().trimmed();
+ if (!responseType.isEmpty()) {
+ signonSessionData.insert(QStringLiteral("ResponseType"), responseType);
+ }
+
+ const QString redirectUri = account->value(QStringLiteral("auth/oauth2/web_server/RedirectUri")).toString().trimmed();
+ if (!redirectUri.isEmpty()) {
+ signonSessionData.insert(QStringLiteral("RedirectUri"), redirectUri);
+ }
+
+ const QVariant scopeValue = account->value(QStringLiteral("auth/oauth2/web_server/Scope"));
+ if (scopeValue.isValid()) {
+ signonSessionData.insert(QStringLiteral("Scope"), scopeValue);
+ }
+
+ const QString clientId = account->value(QStringLiteral("auth/oauth2/web_server/ClientId")).toString().trimmed();
+ if (!clientId.isEmpty()) {
+ signonSessionData.insert(QStringLiteral("ClientId"), clientId);
+ }
+
+ const QString clientSecret = account->value(QStringLiteral("auth/oauth2/web_server/ClientSecret")).toString().trimmed();
+ if (!clientSecret.isEmpty()) {
+ signonSessionData.insert(QStringLiteral("ClientSecret"), clientSecret);
+ }
+
+ signonSessionData.insert(QStringLiteral("UiPolicy"), SignOn::NoUserInteractionPolicy);
+
+ connect(session, SIGNAL(response(SignOn::SessionData)),
+ this, SLOT(signOnResponse(SignOn::SessionData)),
+ Qt::UniqueConnection);
+ connect(session, SIGNAL(error(SignOn::Error)),
+ this, SLOT(signOnError(SignOn::Error)),
+ Qt::UniqueConnection);
+
+ session->setProperty("account", QVariant::fromValue<Accounts::Account*>(account));
+ session->setProperty("identity", QVariant::fromValue<SignOn::Identity*>(identity));
+ session->process(SignOn::SessionData(signonSessionData), mechanism);
+}
+
+void MastodonNotificationsDataTypeSyncAdaptor::signOnError(const SignOn::Error &error)
+{
+ SignOn::AuthSession *session = qobject_cast<SignOn::AuthSession*>(sender());
+ Accounts::Account *account = session->property("account").value<Accounts::Account*>();
+ SignOn::Identity *identity = session->property("identity").value<SignOn::Identity*>();
+ const int accountId = account->id();
+
+ qCWarning(lcSocialPlugin) << "credentials for account with id" << accountId
+ << "couldn't be retrieved:" << error.type() << error.message();
+
+ if (error.type() == SignOn::Error::UserInteraction) {
+ setCredentialsNeedUpdate(account);
+ }
+
+ session->disconnect(this);
+ identity->destroySession(session);
+ identity->deleteLater();
+ account->deleteLater();
+
+ setStatus(SocialNetworkSyncAdaptor::Error);
+ decrementSemaphore(accountId);
+}
+
+void MastodonNotificationsDataTypeSyncAdaptor::signOnResponse(const SignOn::SessionData &responseData)
+{
+ QVariantMap data;
+ foreach (const QString &key, responseData.propertyNames()) {
+ data.insert(key, responseData.getProperty(key));
+ }
+
+ QString accessToken;
+ SignOn::AuthSession *session = qobject_cast<SignOn::AuthSession*>(sender());
+ Accounts::Account *account = session->property("account").value<Accounts::Account*>();
+ SignOn::Identity *identity = session->property("identity").value<SignOn::Identity*>();
+ const int accountId = account->id();
+
+ accessToken = data.value(QLatin1String("AccessToken")).toString().trimmed();
+ if (accessToken.isEmpty()) {
+ accessToken = data.value(QLatin1String("access_token")).toString().trimmed();
+ }
+ if (accessToken.isEmpty()) {
+ qCWarning(lcSocialPlugin) << "signon response for account with id" << accountId
+ << "contained no access token; keys:" << data.keys();
+ }
+
+ m_apiHosts.insert(accountId, normalizeApiHost(account->value(QStringLiteral("api/Host")).toString()));
+
+ session->disconnect(this);
+ identity->destroySession(session);
+ identity->deleteLater();
+ account->deleteLater();
+
+ if (!accessToken.isEmpty()) {
+ beginSync(accountId, accessToken);
+ } else {
+ setStatus(SocialNetworkSyncAdaptor::Error);
+ }
+
+ decrementSemaphore(accountId);
+}
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.h b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.h
new file mode 100644
index 0000000..1c2d13f
--- /dev/null
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodondatatypesyncadaptor.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2013-2026 Jolla Ltd.
+ **
+ ** This program/library is free software; you can redistribute it and/or
+ ** modify it under the terms of the GNU Lesser General Public License
+ ** version 2.1 as published by the Free Software Foundation.
+ **
+ ** This program/library is distributed in the hope that it will be useful,
+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ ** Lesser General Public License for more details.
+ **
+ ** You should have received a copy of the GNU Lesser General Public
+ ** License along with this program/library; if not, write to the Free
+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ ** 02110-1301 USA
+ **
+ ****************************************************************************/
+
+#ifndef MASTODONNOTIFICATIONSDATATYPESYNCADAPTOR_H
+#define MASTODONNOTIFICATIONSDATATYPESYNCADAPTOR_H
+
+#include "socialnetworksyncadaptor.h"
+
+#include <QtCore/QMap>
+#include <QtNetwork/QNetworkReply>
+#include <QtNetwork/QSslError>
+
+namespace Accounts {
+ class Account;
+}
+namespace SignOn {
+ class Error;
+ class SessionData;
+}
+
+class MastodonNotificationsDataTypeSyncAdaptor : public SocialNetworkSyncAdaptor
+{
+ Q_OBJECT
+
+public:
+ MastodonNotificationsDataTypeSyncAdaptor(SocialNetworkSyncAdaptor::DataType dataType, QObject *parent);
+ virtual ~MastodonNotificationsDataTypeSyncAdaptor();
+
+ void sync(const QString &dataTypeString, int accountId) override;
+
+protected:
+ QString apiHost(int accountId) const;
+ virtual void updateDataForAccount(int accountId);
+ virtual void beginSync(int accountId, const QString &accessToken) = 0;
+
+protected Q_SLOTS:
+ virtual void errorHandler(QNetworkReply::NetworkError err);
+ virtual void sslErrorsHandler(const QList<QSslError> &errs);
+
+private Q_SLOTS:
+ void signOnError(const SignOn::Error &error);
+ void signOnResponse(const SignOn::SessionData &responseData);
+
+private:
+ static QString normalizeApiHost(const QString &rawHost);
+ void setCredentialsNeedUpdate(Accounts::Account *account);
+ void signIn(Accounts::Account *account);
+
+private:
+ QMap<int, QString> m_apiHosts;
+};
+
+#endif // MASTODONNOTIFICATIONSDATATYPESYNCADAPTOR_H
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.cpp b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.cpp
new file mode 100644
index 0000000..fe9f989
--- /dev/null
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.cpp
@@ -0,0 +1,49 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2013-2026 Jolla Ltd.
+ **
+ ** This program/library is free software; you can redistribute it and/or
+ ** modify it under the terms of the GNU Lesser General Public License
+ ** version 2.1 as published by the Free Software Foundation.
+ **
+ ** This program/library is distributed in the hope that it will be useful,
+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ ** Lesser General Public License for more details.
+ **
+ ** You should have received a copy of the GNU Lesser General Public
+ ** License along with this program/library; if not, write to the Free
+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ ** 02110-1301 USA
+ **
+ ****************************************************************************/
+
+#include "mastodonnotificationsplugin.h"
+#include "mastodonnotificationssyncadaptor.h"
+#include "socialnetworksyncadaptor.h"
+
+MastodonNotificationsPlugin::MastodonNotificationsPlugin(const QString& pluginName,
+ const Buteo::SyncProfile& profile,
+ Buteo::PluginCbInterface *callbackInterface)
+ : SocialdButeoPlugin(pluginName, profile, callbackInterface,
+ QStringLiteral("mastodon"),
+ SocialNetworkSyncAdaptor::dataTypeName(SocialNetworkSyncAdaptor::Notifications))
+{
+}
+
+MastodonNotificationsPlugin::~MastodonNotificationsPlugin()
+{
+}
+
+SocialNetworkSyncAdaptor *MastodonNotificationsPlugin::createSocialNetworkSyncAdaptor()
+{
+ return new MastodonNotificationsSyncAdaptor(this);
+}
+
+Buteo::ClientPlugin* MastodonNotificationsPluginLoader::createClientPlugin(
+ const QString& pluginName,
+ const Buteo::SyncProfile& profile,
+ Buteo::PluginCbInterface* cbInterface)
+{
+ return new MastodonNotificationsPlugin(pluginName, profile, cbInterface);
+}
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.h b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.h
new file mode 100644
index 0000000..a5a7b37
--- /dev/null
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationsplugin.h
@@ -0,0 +1,54 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2013-2026 Jolla Ltd.
+ **
+ ** This program/library is free software; you can redistribute it and/or
+ ** modify it under the terms of the GNU Lesser General Public License
+ ** version 2.1 as published by the Free Software Foundation.
+ **
+ ** This program/library is distributed in the hope that it will be useful,
+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ ** Lesser General Public License for more details.
+ **
+ ** You should have received a copy of the GNU Lesser General Public
+ ** License along with this program/library; if not, write to the Free
+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ ** 02110-1301 USA
+ **
+ ****************************************************************************/
+
+#ifndef MASTODONNOTIFICATIONSPLUGIN_H
+#define MASTODONNOTIFICATIONSPLUGIN_H
+
+#include "socialdbuteoplugin.h"
+
+#include <buteosyncfw5/SyncPluginLoader.h>
+
+class Q_DECL_EXPORT MastodonNotificationsPlugin : public SocialdButeoPlugin
+{
+ Q_OBJECT
+
+public:
+ MastodonNotificationsPlugin(const QString& pluginName,
+ const Buteo::SyncProfile& profile,
+ Buteo::PluginCbInterface *cbInterface);
+ ~MastodonNotificationsPlugin();
+
+protected:
+ SocialNetworkSyncAdaptor *createSocialNetworkSyncAdaptor() override;
+};
+
+class MastodonNotificationsPluginLoader : public Buteo::SyncPluginLoader
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.sailfishos.plugins.sync.MastodonNotificationsPluginLoader")
+ Q_INTERFACES(Buteo::SyncPluginLoader)
+
+public:
+ Buteo::ClientPlugin* createClientPlugin(const QString& pluginName,
+ const Buteo::SyncProfile& profile,
+ Buteo::PluginCbInterface* cbInterface) override;
+};
+
+#endif // MASTODONNOTIFICATIONSPLUGIN_H
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp
new file mode 100644
index 0000000..aa1089c
--- /dev/null
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp
@@ -0,0 +1,701 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2013-2026 Jolla Ltd.
+ **
+ ** This program/library is free software; you can redistribute it and/or
+ ** modify it under the terms of the GNU Lesser General Public License
+ ** version 2.1 as published by the Free Software Foundation.
+ **
+ ** This program/library is distributed in the hope that it will be useful,
+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ ** Lesser General Public License for more details.
+ **
+ ** You should have received a copy of the GNU Lesser General Public
+ ** License along with this program/library; if not, write to the Free
+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ ** 02110-1301 USA
+ **
+ ****************************************************************************/
+
+#include "mastodonnotificationssyncadaptor.h"
+#include "trace.h"
+
+#include <QtCore/QJsonArray>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonValue>
+#include <QtCore/QRegularExpression>
+#include <QtCore/QUrl>
+#include <QtCore/QUrlQuery>
+#include <QtNetwork/QNetworkRequest>
+
+#include <notification.h>
+
+#define OPEN_BROWSER_ACTION(openUrlArgs) \
+ Notification::remoteAction( \
+ "default", \
+ "", \
+ "org.sailfishos.browser", \
+ "/", \
+ "org.sailfishos.browser", \
+ "openUrl", \
+ QVariantList() << openUrlArgs \
+ )
+
+namespace {
+ const char *const NotificationCategory = "x-nemo.social.mastodon.notification";
+ const char *const LastReadIdProperty = "mastodonLastReadId";
+ const int NotificationsPageLimit = 80;
+ const uint NotificationDismissedReason = 2;
+
+ 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;
+ }
+
+ QString displayNameForAccount(const QJsonObject &account)
+ {
+ const QString displayName = account.value(QStringLiteral("display_name")).toString().trimmed();
+ if (!displayName.isEmpty()) {
+ return displayName;
+ }
+
+ const QString username = account.value(QStringLiteral("username")).toString().trimmed();
+ if (!username.isEmpty()) {
+ return username;
+ }
+
+ return account.value(QStringLiteral("acct")).toString().trimmed();
+ }
+
+ QString actionText(const QString &type)
+ {
+ if (type == QLatin1String("mention")) {
+ return QStringLiteral("mentioned you");
+ } else if (type == QLatin1String("reblog")) {
+ return QStringLiteral("boosted your post");
+ } else if (type == QLatin1String("favourite")) {
+ return QStringLiteral("favourited your post");
+ } else if (type == QLatin1String("follow")) {
+ return QStringLiteral("started following you");
+ } else if (type == QLatin1String("follow_request")) {
+ return QStringLiteral("requested to follow you");
+ } else if (type == QLatin1String("poll")) {
+ return QStringLiteral("interacted with your poll");
+ } else if (type == QLatin1String("status")) {
+ return QStringLiteral("posted");
+ } else if (type == QLatin1String("update")) {
+ return QStringLiteral("updated a post");
+ }
+
+ return QStringLiteral("sent you a notification");
+ }
+
+ QList<QPair<QString, SocialPostImage::ImageType> > parseMediaAttachments(const QJsonObject &statusObject)
+ {
+ QList<QPair<QString, SocialPostImage::ImageType> > imageList;
+
+ const QJsonArray mediaAttachments = statusObject.value(QStringLiteral("media_attachments")).toArray();
+ foreach (const QJsonValue &attachmentValue, mediaAttachments) {
+ const QJsonObject attachment = attachmentValue.toObject();
+ const QString mediaType = attachment.value(QStringLiteral("type")).toString();
+
+ QString mediaUrl;
+ SocialPostImage::ImageType imageType = SocialPostImage::Invalid;
+ if (mediaType == QLatin1String("image")) {
+ mediaUrl = attachment.value(QStringLiteral("url")).toString();
+ imageType = SocialPostImage::Photo;
+ } else if (mediaType == QLatin1String("video") || mediaType == QLatin1String("gifv")) {
+ mediaUrl = attachment.value(QStringLiteral("preview_url")).toString();
+ if (mediaUrl.isEmpty()) {
+ mediaUrl = attachment.value(QStringLiteral("url")).toString();
+ }
+ imageType = SocialPostImage::Video;
+ }
+
+ if (!mediaUrl.isEmpty() && imageType != SocialPostImage::Invalid) {
+ imageList.append(qMakePair(mediaUrl, imageType));
+ }
+ }
+
+ return imageList;
+ }
+}
+
+MastodonNotificationsSyncAdaptor::MastodonNotificationsSyncAdaptor(QObject *parent)
+ : MastodonNotificationsDataTypeSyncAdaptor(SocialNetworkSyncAdaptor::Notifications, parent)
+{
+ setInitialActive(m_db.isValid());
+}
+
+MastodonNotificationsSyncAdaptor::~MastodonNotificationsSyncAdaptor()
+{
+}
+
+QString MastodonNotificationsSyncAdaptor::syncServiceName() const
+{
+ return QStringLiteral("mastodon-microblog");
+}
+
+void MastodonNotificationsSyncAdaptor::purgeDataForOldAccount(int oldId, SocialNetworkSyncAdaptor::PurgeMode)
+{
+ Notification *notification = findNotification(oldId);
+ if (notification) {
+ notification->close();
+ notification->deleteLater();
+ }
+
+ m_db.removePosts(oldId);
+ m_db.commit();
+ m_db.wait();
+
+ purgeCachedImages(&m_imageCacheDb, oldId);
+
+ m_pendingSyncStates.remove(oldId);
+ m_accessTokens.remove(oldId);
+ m_lastMarkedReadIds.remove(oldId);
+}
+
+void MastodonNotificationsSyncAdaptor::beginSync(int accountId, const QString &accessToken)
+{
+ m_accessTokens.insert(accountId, accessToken);
+ m_pendingSyncStates.remove(accountId);
+ requestUnreadMarker(accountId, accessToken);
+}
+
+void MastodonNotificationsSyncAdaptor::finalize(int accountId)
+{
+ if (syncAborted()) {
+ qCInfo(lcSocialPlugin) << "sync aborted, won't commit database changes";
+ } else {
+ m_db.commit();
+ m_db.wait();
+ purgeExpiredImages(&m_imageCacheDb, 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();
+}
+
+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;
+}
+
+int MastodonNotificationsSyncAdaptor::compareNotificationIds(const QString &left, const QString &right)
+{
+ if (left == right) {
+ return 0;
+ }
+
+ bool leftOk = false;
+ bool rightOk = false;
+ const qulonglong leftValue = left.toULongLong(&leftOk);
+ const qulonglong rightValue = right.toULongLong(&rightOk);
+ if (leftOk && rightOk) {
+ return leftValue < rightValue ? -1 : 1;
+ }
+
+ if (left.size() != right.size()) {
+ return left.size() < right.size() ? -1 : 1;
+ }
+ return left < right ? -1 : 1;
+}
+
+void MastodonNotificationsSyncAdaptor::requestUnreadMarker(int accountId, const QString &accessToken)
+{
+ QUrl url(apiHost(accountId) + QStringLiteral("/api/v1/markers"));
+
+ QUrlQuery query(url);
+ query.addQueryItem(QStringLiteral("timeline[]"), QStringLiteral("notifications"));
+ url.setQuery(query);
+
+ QNetworkRequest request(url);
+ request.setRawHeader("Authorization", QStringLiteral("Bearer %1").arg(accessToken).toUtf8());
+
+ QNetworkReply *reply = m_networkAccessManager->get(request);
+ if (reply) {
+ reply->setProperty("accountId", accountId);
+ reply->setProperty("accessToken", accessToken);
+ connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(errorHandler(QNetworkReply::NetworkError)));
+ connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrorsHandler(QList<QSslError>)));
+ connect(reply, SIGNAL(finished()), this, SLOT(finishedUnreadMarkerHandler()));
+
+ incrementSemaphore(accountId);
+ setupReplyTimeout(accountId, reply);
+ } else {
+ qCWarning(lcSocialPlugin) << "unable to request notifications marker from Mastodon account with id" << accountId;
+ }
+}
+
+void MastodonNotificationsSyncAdaptor::requestNotifications(int accountId,
+ const QString &accessToken,
+ const QString &minId,
+ const QString &maxId)
+{
+ QUrl url(apiHost(accountId) + QStringLiteral("/api/v1/notifications"));
+
+ QUrlQuery query(url);
+ query.addQueryItem(QStringLiteral("limit"), QString::number(NotificationsPageLimit));
+ if (!minId.isEmpty()) {
+ query.addQueryItem(QStringLiteral("min_id"), minId);
+ }
+ if (!maxId.isEmpty()) {
+ query.addQueryItem(QStringLiteral("max_id"), maxId);
+ }
+ url.setQuery(query);
+
+ QNetworkRequest request(url);
+ request.setRawHeader("Authorization", QStringLiteral("Bearer %1").arg(accessToken).toUtf8());
+
+ QNetworkReply *reply = m_networkAccessManager->get(request);
+ if (reply) {
+ reply->setProperty("accountId", accountId);
+ reply->setProperty("accessToken", accessToken);
+ reply->setProperty("minId", minId);
+ connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(errorHandler(QNetworkReply::NetworkError)));
+ connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrorsHandler(QList<QSslError>)));
+ connect(reply, SIGNAL(finished()), this, SLOT(finishedNotificationsHandler()));
+
+ incrementSemaphore(accountId);
+ setupReplyTimeout(accountId, reply);
+ } else {
+ qCWarning(lcSocialPlugin) << "unable to request notifications from Mastodon account with id" << accountId;
+ }
+}
+
+void MastodonNotificationsSyncAdaptor::finishedUnreadMarkerHandler()
+{
+ QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
+ if (!reply) {
+ return;
+ }
+
+ const bool isError = reply->property("isError").toBool();
+ const int accountId = reply->property("accountId").toInt();
+ const QString accessToken = reply->property("accessToken").toString();
+ const QByteArray replyData = reply->readAll();
+
+ disconnect(reply);
+ reply->deleteLater();
+ removeReplyTimeout(accountId, reply);
+
+ QString minReadId;
+ bool ok = false;
+ const QJsonObject markerObject = parseJsonObjectReplyData(replyData, &ok);
+ if (!isError && ok) {
+ minReadId = markerObject.value(QStringLiteral("notifications"))
+ .toObject()
+ .value(QStringLiteral("last_read_id"))
+ .toVariant()
+ .toString()
+ .trimmed();
+ } else {
+ qCWarning(lcSocialPlugin) << "unable to parse notifications marker data from request with account" << accountId
+ << ", got:" << QString::fromUtf8(replyData);
+ decrementSemaphore(accountId);
+ return;
+ }
+
+ PendingSyncState state;
+ state.accessToken = accessToken;
+ state.minReadId = minReadId;
+ state.maxNotificationId = minReadId;
+ m_pendingSyncStates.insert(accountId, state);
+
+ requestNotifications(accountId, accessToken, minReadId);
+ decrementSemaphore(accountId);
+}
+
+void MastodonNotificationsSyncAdaptor::finishedNotificationsHandler()
+{
+ QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
+ if (!reply) {
+ return;
+ }
+
+ const bool isError = reply->property("isError").toBool();
+ const int accountId = reply->property("accountId").toInt();
+ const QString accessToken = reply->property("accessToken").toString();
+ const QString minId = reply->property("minId").toString();
+ const QByteArray replyData = reply->readAll();
+
+ disconnect(reply);
+ reply->deleteLater();
+ removeReplyTimeout(accountId, reply);
+
+ PendingSyncState state = m_pendingSyncStates.value(accountId);
+ if (state.accessToken.isEmpty()) {
+ state.accessToken = accessToken;
+ state.minReadId = minId;
+ state.maxNotificationId = minId;
+ }
+
+ bool ok = false;
+ const QJsonArray notifications = parseJsonArrayReplyData(replyData, &ok);
+ if (!isError && ok) {
+ if (!notifications.size()) {
+ if (!state.dbCleared) {
+ m_db.removePosts(accountId);
+ state.dbCleared = true;
+ }
+ Notification *notification = findNotification(accountId);
+ if (notification) {
+ notification->close();
+ notification->deleteLater();
+ }
+ qCDebug(lcSocialPlugin) << "no notifications received for account" << accountId;
+ m_pendingSyncStates.remove(accountId);
+ decrementSemaphore(accountId);
+ return;
+ }
+
+ if (!state.dbCleared) {
+ m_db.removePosts(accountId);
+ state.dbCleared = true;
+ }
+
+ QString pageMinNotificationId;
+
+ foreach (const QJsonValue &notificationValue, notifications) {
+ const QJsonObject notificationObject = notificationValue.toObject();
+ if (notificationObject.isEmpty()) {
+ continue;
+ }
+
+ const QString notificationId = notificationObject.value(QStringLiteral("id")).toVariant().toString();
+ if (notificationId.isEmpty()) {
+ continue;
+ }
+
+ if (pageMinNotificationId.isEmpty()
+ || compareNotificationIds(notificationId, pageMinNotificationId) < 0) {
+ pageMinNotificationId = notificationId;
+ }
+ if (state.maxNotificationId.isEmpty()
+ || compareNotificationIds(notificationId, state.maxNotificationId) > 0) {
+ state.maxNotificationId = notificationId;
+ }
+
+ const QString notificationType = notificationObject.value(QStringLiteral("type")).toString();
+ const QJsonObject actorObject = notificationObject.value(QStringLiteral("account")).toObject();
+ const QJsonValue statusValue = notificationObject.value(QStringLiteral("status"));
+ const QJsonObject statusObject = statusValue.isObject() && !statusValue.isNull()
+ ? statusValue.toObject()
+ : QJsonObject();
+
+ QDateTime eventTimestamp = parseTimestamp(notificationObject.value(QStringLiteral("created_at")).toString());
+ if (!eventTimestamp.isValid()) {
+ eventTimestamp = parseTimestamp(statusObject.value(QStringLiteral("created_at")).toString());
+ }
+ if (!eventTimestamp.isValid()) {
+ continue;
+ }
+
+ const QString displayName = displayNameForAccount(actorObject);
+ const QString accountName = actorObject.value(QStringLiteral("acct")).toString();
+
+ QString icon = actorObject.value(QStringLiteral("avatar_static")).toString();
+ if (icon.isEmpty()) {
+ icon = actorObject.value(QStringLiteral("avatar")).toString();
+ }
+
+ const QString statusBody = sanitizeContent(statusObject.value(QStringLiteral("content")).toString());
+ const QString action = actionText(notificationType);
+ QString body;
+ if (notificationType == QLatin1String("mention")
+ || notificationType == QLatin1String("status")
+ || notificationType == QLatin1String("update")) {
+ body = statusBody.isEmpty() ? action : statusBody;
+ } else {
+ body = statusBody.isEmpty() ? action : QStringLiteral("%1: %2").arg(action, statusBody);
+ }
+
+ const QString statusId = statusObject.value(QStringLiteral("id")).toVariant().toString();
+
+ QString url = statusObject.value(QStringLiteral("url")).toString();
+ if (url.isEmpty()) {
+ url = statusObject.value(QStringLiteral("uri")).toString();
+ }
+ if (url.isEmpty()) {
+ url = actorObject.value(QStringLiteral("url")).toString();
+ }
+ if (url.isEmpty() && !accountName.isEmpty() && !statusId.isEmpty()) {
+ url = QStringLiteral("%1/@%2/%3").arg(apiHost(accountId), accountName, statusId);
+ } else if (url.isEmpty() && !accountName.isEmpty()) {
+ url = QStringLiteral("%1/@%2").arg(apiHost(accountId), accountName);
+ }
+
+ QString boostedBy;
+ if (notificationType == QLatin1String("reblog")
+ || notificationType == QLatin1String("favourite")) {
+ boostedBy = displayName;
+ }
+
+ const QList<QPair<QString, SocialPostImage::ImageType> > imageList = parseMediaAttachments(statusObject);
+
+ m_db.addMastodonNotification(QStringLiteral("n:%1").arg(notificationId),
+ displayName,
+ accountName,
+ body,
+ eventTimestamp,
+ icon,
+ imageList,
+ url,
+ boostedBy,
+ apiHost(accountId),
+ accountId);
+
+ ++state.newNotificationCount;
+ if (state.newNotificationCount == 1) {
+ state.singleSummary = displayName;
+ state.singleBody = body;
+ state.singleLink = url;
+ state.singleTimestamp = eventTimestamp;
+ }
+ }
+
+ if (notifications.size() >= NotificationsPageLimit
+ && !pageMinNotificationId.isEmpty()
+ && (state.minReadId.isEmpty() || compareNotificationIds(pageMinNotificationId, state.minReadId) > 0)) {
+ m_pendingSyncStates.insert(accountId, state);
+ requestNotifications(accountId, state.accessToken, state.minReadId, pageMinNotificationId);
+ decrementSemaphore(accountId);
+ return;
+ }
+
+ if (state.newNotificationCount > 0) {
+ publishSystemNotification(accountId, state);
+ }
+ } else {
+ qCWarning(lcSocialPlugin) << "unable to parse notifications data from request with account" << accountId
+ << ", got:" << QString::fromUtf8(replyData);
+ }
+
+ m_pendingSyncStates.remove(accountId);
+ decrementSemaphore(accountId);
+}
+
+void MastodonNotificationsSyncAdaptor::requestMarkRead(int accountId,
+ const QString &accessToken,
+ const QString &lastReadId)
+{
+ QUrl url(apiHost(accountId) + QStringLiteral("/api/v1/markers"));
+ QNetworkRequest request(url);
+ request.setRawHeader("Authorization", QStringLiteral("Bearer %1").arg(accessToken).toUtf8());
+ request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded"));
+
+ QUrlQuery query;
+ query.addQueryItem(QStringLiteral("notifications[last_read_id]"), lastReadId);
+ const QByteArray payload = query.toString(QUrl::FullyEncoded).toUtf8();
+
+ QNetworkReply *reply = m_networkAccessManager->post(request, payload);
+ if (reply) {
+ reply->setProperty("accountId", accountId);
+ reply->setProperty("lastReadId", lastReadId);
+ connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(errorHandler(QNetworkReply::NetworkError)));
+ connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrorsHandler(QList<QSslError>)));
+ connect(reply, SIGNAL(finished()), this, SLOT(finishedMarkReadHandler()));
+
+ incrementSemaphore(accountId);
+ setupReplyTimeout(accountId, reply);
+ } else {
+ qCWarning(lcSocialPlugin) << "unable to mark notifications read for Mastodon account with id" << accountId;
+ }
+}
+
+void MastodonNotificationsSyncAdaptor::finishedMarkReadHandler()
+{
+ QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
+ if (!reply) {
+ return;
+ }
+
+ const bool isError = reply->property("isError").toBool();
+ const int accountId = reply->property("accountId").toInt();
+ const QString lastReadId = reply->property("lastReadId").toString();
+ const QByteArray replyData = reply->readAll();
+
+ disconnect(reply);
+ reply->deleteLater();
+ removeReplyTimeout(accountId, reply);
+
+ bool ok = false;
+ parseJsonObjectReplyData(replyData, &ok);
+ if (!isError && ok) {
+ m_lastMarkedReadIds.insert(accountId, lastReadId);
+ } else {
+ qCWarning(lcSocialPlugin) << "unable to mark notifications read for account" << accountId
+ << ", got:" << QString::fromUtf8(replyData);
+ }
+
+ decrementSemaphore(accountId);
+}
+
+void MastodonNotificationsSyncAdaptor::publishSystemNotification(int accountId, const PendingSyncState &state)
+{
+ Notification *notification = createNotification(accountId);
+ notification->setItemCount(state.newNotificationCount);
+
+ QStringList openUrlArgs;
+ if (notification->itemCount() == 1) {
+ notification->setTimestamp(state.singleTimestamp.isValid() ? state.singleTimestamp : QDateTime::currentDateTimeUtc());
+ notification->setSummary(state.singleSummary.isEmpty() ? QStringLiteral("Mastodon") : state.singleSummary);
+ notification->setBody(state.singleBody.isEmpty() ? QStringLiteral("New notification") : state.singleBody);
+ openUrlArgs << (state.singleLink.isEmpty() ? apiHost(accountId) + QStringLiteral("/notifications") : state.singleLink);
+ } else {
+ notification->setTimestamp(QDateTime::currentDateTimeUtc());
+ notification->setSummary(QStringLiteral("Mastodon"));
+ notification->setBody(QStringLiteral("You have %1 new notifications").arg(notification->itemCount()));
+ openUrlArgs << apiHost(accountId) + QStringLiteral("/notifications");
+ }
+
+ notification->setProperty(LastReadIdProperty, state.maxNotificationId);
+ notification->setUrgency(Notification::Low);
+ notification->setRemoteAction(OPEN_BROWSER_ACTION(openUrlArgs));
+ notification->publish();
+ if (notification->replacesId() == 0) {
+ qCWarning(lcSocialPlugin) << "failed to publish Mastodon notification";
+ }
+}
+
+void MastodonNotificationsSyncAdaptor::notificationClosedWithReason(uint reason)
+{
+ if (reason == NotificationDismissedReason) {
+ markReadFromNotification(qobject_cast<Notification *>(sender()));
+ }
+}
+
+void MastodonNotificationsSyncAdaptor::markReadFromNotification(Notification *notification)
+{
+ if (!notification) {
+ return;
+ }
+
+ const int accountId = notification->hintValue("x-nemo.sociald.account-id").toInt();
+ const QString lastReadId = notification->property(LastReadIdProperty).toString().trimmed();
+ if (accountId <= 0 || lastReadId.isEmpty()) {
+ return;
+ }
+
+ if (m_lastMarkedReadIds.value(accountId) == lastReadId) {
+ return;
+ }
+
+ const QString accessToken = m_accessTokens.value(accountId).trimmed();
+ if (accessToken.isEmpty()) {
+ qCWarning(lcSocialPlugin) << "cannot mark notifications read for account" << accountId
+ << "- missing access token";
+ return;
+ }
+
+ notification->setProperty(LastReadIdProperty, QString());
+ requestMarkRead(accountId, accessToken, lastReadId);
+}
+
+Notification *MastodonNotificationsSyncAdaptor::createNotification(int accountId)
+{
+ Notification *notification = findNotification(accountId);
+ if (!notification) {
+ notification = new Notification(this);
+ notification->setAppName(QStringLiteral("Mastodon"));
+ notification->setAppIcon(QStringLiteral("icon-l-mastodon"));
+ notification->setHintValue("x-nemo.sociald.account-id", accountId);
+ notification->setHintValue("x-nemo-feedback", QStringLiteral("social"));
+ notification->setCategory(QLatin1String(NotificationCategory));
+ }
+ connect(notification, SIGNAL(closed(uint)), this, SLOT(notificationClosedWithReason(uint)), Qt::UniqueConnection);
+
+ return notification;
+}
+
+Notification *MastodonNotificationsSyncAdaptor::findNotification(int accountId)
+{
+ Notification *notification = 0;
+ QList<QObject *> notifications = Notification::notifications();
+ foreach (QObject *object, notifications) {
+ Notification *castedNotification = static_cast<Notification *>(object);
+ if (castedNotification->category() == QLatin1String(NotificationCategory)
+ && castedNotification->hintValue("x-nemo.sociald.account-id").toInt() == accountId) {
+ notification = castedNotification;
+ break;
+ }
+ }
+
+ if (notification) {
+ notifications.removeAll(notification);
+ }
+
+ qDeleteAll(notifications);
+
+ return notification;
+}
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.h b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.h
new file mode 100644
index 0000000..8c79d7d
--- /dev/null
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2013-2026 Jolla Ltd.
+ **
+ ** This program/library is free software; you can redistribute it and/or
+ ** modify it under the terms of the GNU Lesser General Public License
+ ** version 2.1 as published by the Free Software Foundation.
+ **
+ ** This program/library is distributed in the hope that it will be useful,
+ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ ** Lesser General Public License for more details.
+ **
+ ** You should have received a copy of the GNU Lesser General Public
+ ** License along with this program/library; if not, write to the Free
+ ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ ** 02110-1301 USA
+ **
+ ****************************************************************************/
+
+#ifndef MASTODONNOTIFICATIONSSYNCADAPTOR_H
+#define MASTODONNOTIFICATIONSSYNCADAPTOR_H
+
+#include "mastodondatatypesyncadaptor.h"
+
+#include <QtCore/QDateTime>
+#include <QtCore/QHash>
+#include <QtNetwork/QNetworkReply>
+
+#include "mastodonnotificationsdatabase.h"
+#include <socialcache/socialimagesdatabase.h>
+
+class Notification;
+
+class MastodonNotificationsSyncAdaptor : public MastodonNotificationsDataTypeSyncAdaptor
+{
+ Q_OBJECT
+
+public:
+ MastodonNotificationsSyncAdaptor(QObject *parent);
+ ~MastodonNotificationsSyncAdaptor();
+
+ QString syncServiceName() const override;
+
+protected:
+ void purgeDataForOldAccount(int oldId, SocialNetworkSyncAdaptor::PurgeMode mode) override;
+ void beginSync(int accountId, const QString &accessToken) override;
+ void finalize(int accountId) override;
+
+private:
+ struct PendingSyncState {
+ QString accessToken;
+ QString minReadId;
+ QString maxNotificationId;
+ int newNotificationCount;
+ QString singleSummary;
+ QString singleBody;
+ QString singleLink;
+ QDateTime singleTimestamp;
+ bool dbCleared;
+
+ PendingSyncState()
+ : newNotificationCount(0)
+ , dbCleared(false)
+ {
+ }
+ };
+
+ static QString sanitizeContent(const QString &content);
+ static QDateTime parseTimestamp(const QString &timestampString);
+ static int compareNotificationIds(const QString &left, const QString &right);
+
+ void requestUnreadMarker(int accountId, const QString &accessToken);
+ void requestNotifications(int accountId,
+ const QString &accessToken,
+ const QString &minId,
+ const QString &maxId = QString());
+ void requestMarkRead(int accountId, const QString &accessToken, const QString &lastReadId);
+ void publishSystemNotification(int accountId, const PendingSyncState &state);
+ Notification *createNotification(int accountId);
+ Notification *findNotification(int accountId);
+ void markReadFromNotification(Notification *notification);
+
+private Q_SLOTS:
+ void finishedUnreadMarkerHandler();
+ void finishedNotificationsHandler();
+ void finishedMarkReadHandler();
+ void notificationClosedWithReason(uint reason);
+
+private:
+ MastodonNotificationsDatabase m_db;
+ SocialImagesDatabase m_imageCacheDb;
+ QHash<int, PendingSyncState> m_pendingSyncStates;
+ QHash<int, QString> m_accessTokens;
+ QHash<int, QString> m_lastMarkedReadIds;
+};
+
+#endif // MASTODONNOTIFICATIONSSYNCADAPTOR_H
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodondatatypesyncadaptor.cpp b/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodondatatypesyncadaptor.cpp
index 977b33b..83a5249 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodondatatypesyncadaptor.cpp
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodondatatypesyncadaptor.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2026 Open Mobile Platform LLC.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
**
** This program/library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public License
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodondatatypesyncadaptor.h b/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodondatatypesyncadaptor.h
index f295cd8..ad8321d 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodondatatypesyncadaptor.h
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodondatatypesyncadaptor.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2026 Open Mobile Platform LLC.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
**
** This program/library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public License
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostsplugin.cpp b/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostsplugin.cpp
index d3c8d50..a196180 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostsplugin.cpp
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostsplugin.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2026 Open Mobile Platform LLC.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
**
** This program/library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public License
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostsplugin.h b/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostsplugin.h
index 35dbb56..c8a1d6b 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostsplugin.h
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostsplugin.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2026 Open Mobile Platform LLC.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
**
** This program/library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public License
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostssyncadaptor.cpp b/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostssyncadaptor.cpp
index 7ccf0a2..c7c696e 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostssyncadaptor.cpp
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostssyncadaptor.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2026 Open Mobile Platform LLC.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
**
** This program/library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public License
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostssyncadaptor.h b/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostssyncadaptor.h
index 10f8b1c..d2ca464 100644
--- a/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostssyncadaptor.h
+++ b/buteo-plugins/buteo-sync-plugin-mastodon-posts/mastodonpostssyncadaptor.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2026 Open Mobile Platform LLC.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
**
** This program/library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public License
diff --git a/common/common.pro b/common/common.pro
index eec5b5f..89f0117 100644
--- a/common/common.pro
+++ b/common/common.pro
@@ -10,10 +10,12 @@ TARGET = mastodoncommon
TARGET = $$qtLibraryTarget($$TARGET)
HEADERS += \
- $$PWD/mastodonpostsdatabase.h
+ $$PWD/mastodonpostsdatabase.h \
+ $$PWD/mastodonnotificationsdatabase.h
SOURCES += \
- $$PWD/mastodonpostsdatabase.cpp
+ $$PWD/mastodonpostsdatabase.cpp \
+ $$PWD/mastodonnotificationsdatabase.cpp
TARGETPATH = $$[QT_INSTALL_LIBS]
target.path = $$TARGETPATH
diff --git a/common/mastodonnotificationsdatabase.cpp b/common/mastodonnotificationsdatabase.cpp
new file mode 100644
index 0000000..abb55b9
--- /dev/null
+++ b/common/mastodonnotificationsdatabase.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013-2026 Jolla Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "mastodonnotificationsdatabase.h"
+
+static const char *DB_NAME = "mastodonNotifications.db";
+static const char *ACCOUNT_NAME_KEY = "account_name";
+static const char *URL_KEY = "url";
+static const char *BOOSTED_BY_KEY = "boosted_by";
+static const char *INSTANCE_URL_KEY = "instance_url";
+
+MastodonNotificationsDatabase::MastodonNotificationsDatabase()
+ : AbstractSocialPostCacheDatabase(QStringLiteral("mastodon"), QLatin1String(DB_NAME))
+{
+}
+
+MastodonNotificationsDatabase::~MastodonNotificationsDatabase()
+{
+}
+
+void MastodonNotificationsDatabase::addMastodonNotification(
+ const QString &identifier,
+ const QString &name,
+ const QString &accountName,
+ const QString &body,
+ const QDateTime &timestamp,
+ const QString &icon,
+ const QList<QPair<QString, SocialPostImage::ImageType> > &images,
+ const QString &url,
+ const QString &boostedBy,
+ const QString &instanceUrl,
+ int account)
+{
+ QVariantMap extra;
+ extra.insert(ACCOUNT_NAME_KEY, accountName);
+ extra.insert(URL_KEY, url);
+ extra.insert(BOOSTED_BY_KEY, boostedBy);
+ extra.insert(INSTANCE_URL_KEY, instanceUrl);
+ addPost(identifier, name, body, timestamp, icon, images, extra, account);
+}
+
+QString MastodonNotificationsDatabase::accountName(const SocialPost::ConstPtr &post)
+{
+ if (post.isNull()) {
+ return QString();
+ }
+ return post->extra().value(ACCOUNT_NAME_KEY).toString();
+}
+
+QString MastodonNotificationsDatabase::url(const SocialPost::ConstPtr &post)
+{
+ if (post.isNull()) {
+ return QString();
+ }
+ return post->extra().value(URL_KEY).toString();
+}
+
+QString MastodonNotificationsDatabase::boostedBy(const SocialPost::ConstPtr &post)
+{
+ if (post.isNull()) {
+ return QString();
+ }
+ return post->extra().value(BOOSTED_BY_KEY).toString();
+}
+
+QString MastodonNotificationsDatabase::instanceUrl(const SocialPost::ConstPtr &post)
+{
+ if (post.isNull()) {
+ return QString();
+ }
+ return post->extra().value(INSTANCE_URL_KEY).toString();
+}
diff --git a/common/mastodonnotificationsdatabase.h b/common/mastodonnotificationsdatabase.h
new file mode 100644
index 0000000..cc69095
--- /dev/null
+++ b/common/mastodonnotificationsdatabase.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013-2026 Jolla Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef MASTODONNOTIFICATIONSDATABASE_H
+#define MASTODONNOTIFICATIONSDATABASE_H
+
+#include <socialcache/abstractsocialpostcachedatabase.h>
+
+class MastodonNotificationsDatabase: public AbstractSocialPostCacheDatabase
+{
+ Q_OBJECT
+public:
+ MastodonNotificationsDatabase();
+ ~MastodonNotificationsDatabase();
+
+ void addMastodonNotification(const QString &identifier, const QString &name,
+ const QString &accountName, const QString &body,
+ const QDateTime &timestamp,
+ const QString &icon,
+ const QList<QPair<QString, SocialPostImage::ImageType> > &images,
+ const QString &url, const QString &boostedBy,
+ const QString &instanceUrl,
+ int account);
+
+ static QString accountName(const SocialPost::ConstPtr &post);
+ static QString url(const SocialPost::ConstPtr &post);
+ static QString boostedBy(const SocialPost::ConstPtr &post);
+ static QString instanceUrl(const SocialPost::ConstPtr &post);
+};
+
+#endif // MASTODONNOTIFICATIONSDATABASE_H
diff --git a/common/mastodonpostsdatabase.cpp b/common/mastodonpostsdatabase.cpp
index fa6ddd3..08fc777 100644
--- a/common/mastodonpostsdatabase.cpp
+++ b/common/mastodonpostsdatabase.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2026 Open Mobile Platform LLC.
+ * Copyright (C) 2013-2026 Jolla Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
diff --git a/common/mastodonpostsdatabase.h b/common/mastodonpostsdatabase.h
index d5fdea7..b27b626 100644
--- a/common/mastodonpostsdatabase.h
+++ b/common/mastodonpostsdatabase.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2026 Open Mobile Platform LLC.
+ * Copyright (C) 2013-2026 Jolla Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
diff --git a/eventsview-plugins/eventsview-plugin-mastodon/MastodonFeedItem.qml b/eventsview-plugins/eventsview-plugin-mastodon/MastodonFeedItem.qml
index 231b814..c96fb8a 100644
--- a/eventsview-plugins/eventsview-plugin-mastodon/MastodonFeedItem.qml
+++ b/eventsview-plugins/eventsview-plugin-mastodon/MastodonFeedItem.qml
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2026 Open Mobile Platform LLC.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
**
****************************************************************************/
diff --git a/eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel.cpp b/eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel.cpp
index 6d33d48..895ad72 100644
--- a/eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel.cpp
+++ b/eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Jolla Ltd.
+ * Copyright (C) 2013-2026 Jolla Ltd.
* Contact: Lucien Xu <lucien.xu@jollamobile.com>
*
* This library is free software; you can redistribute it and/or
diff --git a/eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel.h b/eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel.h
index 1e6394f..c3c3ffe 100644
--- a/eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel.h
+++ b/eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Jolla Ltd.
+ * Copyright (C) 2013-2026 Jolla Ltd.
* Contact: Lucien Xu <lucien.xu@jollamobile.com>
*
* This library is free software; you can redistribute it and/or
diff --git a/eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel_p.h b/eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel_p.h
index 6c92655..fa62ac5 100644
--- a/eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel_p.h
+++ b/eventsview-plugins/eventsview-plugin-mastodon/abstractsocialcachemodel_p.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Jolla Ltd.
+ * Copyright (C) 2013-2026 Jolla Ltd.
* Contact: Lucien Xu <lucien.xu@jollamobile.com>
*
* This library is free software; you can redistribute it and/or
diff --git a/eventsview-plugins/eventsview-plugin-mastodon/mastodon-delegate.qml b/eventsview-plugins/eventsview-plugin-mastodon/mastodon-delegate.qml
index ed79fdb..906dc9c 100644
--- a/eventsview-plugins/eventsview-plugin-mastodon/mastodon-delegate.qml
+++ b/eventsview-plugins/eventsview-plugin-mastodon/mastodon-delegate.qml
@@ -1,6 +1,6 @@
/****************************************************************************
**
- ** Copyright (C) 2026 Open Mobile Platform LLC.
+ ** Copyright (C) 2013-2026 Jolla Ltd.
**
****************************************************************************/
@@ -17,10 +17,10 @@ SocialMediaAccountDelegate {
//: Mastodon posts
//% "Posts"
headerText: qsTrId("lipstick-jolla-home-la-mastodon_posts")
- headerIcon: "image://theme/graphic-service-mastodon"
+ headerIcon: "image://theme/icon-l-mastodon"
showRemainingCount: false
- services: ["Posts", "Notifications"]
+ services: ["Posts"]
socialNetwork: 9
dataType: SocialSync.Posts
providerName: "mastodon"
diff --git a/eventsview-plugins/eventsview-plugin-mastodon/mastodonpostsmodel.cpp b/eventsview-plugins/eventsview-plugin-mastodon/mastodonpostsmodel.cpp
index 3e54b8b..74d239e 100644
--- a/eventsview-plugins/eventsview-plugin-mastodon/mastodonpostsmodel.cpp
+++ b/eventsview-plugins/eventsview-plugin-mastodon/mastodonpostsmodel.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2026 Open Mobile Platform LLC.
+ * Copyright (C) 2013-2026 Jolla Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
diff --git a/eventsview-plugins/eventsview-plugin-mastodon/mastodonpostsmodel.h b/eventsview-plugins/eventsview-plugin-mastodon/mastodonpostsmodel.h
index 9692729..150438a 100644
--- a/eventsview-plugins/eventsview-plugin-mastodon/mastodonpostsmodel.h
+++ b/eventsview-plugins/eventsview-plugin-mastodon/mastodonpostsmodel.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2026 Open Mobile Platform LLC.
+ * Copyright (C) 2013-2026 Jolla Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
diff --git a/eventsview-plugins/eventsview-plugin-mastodon/postimagehelper_p.h b/eventsview-plugins/eventsview-plugin-mastodon/postimagehelper_p.h
index fe61212..0d70ffa 100644
--- a/eventsview-plugins/eventsview-plugin-mastodon/postimagehelper_p.h
+++ b/eventsview-plugins/eventsview-plugin-mastodon/postimagehelper_p.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Jolla Ltd.
+ * Copyright (C) 2013-2026 Jolla Ltd.
* Contact: Lucien Xu <lucien.xu@jollamobile.com>
*
* This library is free software; you can redistribute it and/or
diff --git a/eventsview-plugins/eventsview-plugin-mastodon/synchronizelists_p.h b/eventsview-plugins/eventsview-plugin-mastodon/synchronizelists_p.h
index 1e09e86..78d5863 100644
--- a/eventsview-plugins/eventsview-plugin-mastodon/synchronizelists_p.h
+++ b/eventsview-plugins/eventsview-plugin-mastodon/synchronizelists_p.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Jolla Mobile <andrew.den.exter@jollamobile.com>
+ * Copyright (C) 2013-2026 Jolla Ltd.
*
* You may use this file under the terms of the BSD license as follows:
*
diff --git a/rpm/sailfish-account-mastodon.spec b/rpm/sailfish-account-mastodon.spec
index 1fac766..1187379 100644
--- a/rpm/sailfish-account-mastodon.spec
+++ b/rpm/sailfish-account-mastodon.spec
@@ -23,6 +23,10 @@ BuildRequires: pkgconfig(nemotransferengine-qt5) >= 2.0.0
BuildRequires: pkgconfig(nemonotifications-qt5)
Requires: jolla-settings-accounts-extensions-onlinesync
Requires: qmf-oauth2-plugin >= 0.0.7
+Requires: buteo-sync-plugin-mastodon-posts
+Requires: buteo-sync-plugin-mastodon-notifications
+Requires: eventsview-extensions-mastodon
+Requires: transferengine-plugin-mastodon
Requires(post): %{_libexecdir}/manage-groups
Requires(postun): %{_libexecdir}/manage-groups
@@ -39,6 +43,17 @@ Requires(post): systemd
%description -n buteo-sync-plugin-mastodon-posts
Provides synchronisation of Mastodon posts.
+%package -n buteo-sync-plugin-mastodon-notifications
+Summary: Provides synchronisation of Mastodon notifications
+Requires: %{name} = %{version}-%{release}
+Requires: buteo-syncfw-qt5-msyncd
+Requires: systemd
+Requires(post): systemd
+
+%description -n buteo-sync-plugin-mastodon-notifications
+Provides synchronisation of Mastodon notifications.
+
+
%package -n eventsview-extensions-mastodon
Summary: Provides integration of Mastodon posts into Events view
Requires: lipstick-jolla-home-qt5-components >= 1.2.50
@@ -57,16 +72,6 @@ Requires: %{name} = %{version}-%{release}
%description -n transferengine-plugin-mastodon
Mastodon image sharing plugin for Transfer Engine.
-%package features-all
-Summary: Meta package to include all Mastodon account features
-Requires: %{name} = %{version}-%{release}
-Requires: buteo-sync-plugin-mastodon-posts
-Requires: eventsview-extensions-mastodon
-Requires: transferengine-plugin-mastodon
-
-%description features-all
-This package includes all Mastodon account features.
-
%prep
%setup -q -n %{name}-%{version}
@@ -78,12 +83,6 @@ This package includes all Mastodon account features.
%qmake5_install
cd icons
make INSTALL_ROOT=%{buildroot} install
-for icon in $(find %{buildroot}%{_datadir}/themes/sailfish-default/silica -type f -name icon-l-mastodon.png); do
- dir=$(dirname "$icon")
- cp -a "$icon" "$dir/graphic-service-mastodon.png"
- cp -a "$icon" "$dir/graphic-m-service-mastodon.png"
- cp -a "$icon" "$dir/graphic-s-service-mastodon.png"
-done
%post
/sbin/ldconfig
@@ -107,9 +106,6 @@ fi
%{_datadir}/accounts/ui/mastodon.qml
%{_datadir}/accounts/ui/mastodon-settings.qml
%{_datadir}/accounts/ui/mastodon-update.qml
-%{_datadir}/themes/sailfish-default/silica/*/icons/graphic-service-mastodon.png
-%{_datadir}/themes/sailfish-default/silica/*/icons/graphic-m-service-mastodon.png
-%{_datadir}/themes/sailfish-default/silica/*/icons/graphic-s-service-mastodon.png
%{_datadir}/themes/sailfish-default/silica/*/icons/icon-l-mastodon.png
%files -n buteo-sync-plugin-mastodon-posts
@@ -117,6 +113,11 @@ fi
%config %{_sysconfdir}/buteo/profiles/client/mastodon-posts.xml
%config %{_sysconfdir}/buteo/profiles/sync/mastodon.Posts.xml
+%files -n buteo-sync-plugin-mastodon-notifications
+%{_libdir}/buteo-plugins-qt5/oopp/libmastodon-notifications-client.so
+%config %{_sysconfdir}/buteo/profiles/client/mastodon-notifications.xml
+%config %{_sysconfdir}/buteo/profiles/sync/mastodon.Notifications.xml
+
%files -n eventsview-extensions-mastodon
%{_libdir}/qt5/qml/com/jolla/eventsview/mastodon/*
%{_datadir}/lipstick/eventfeed/mastodon-delegate.qml
@@ -128,6 +129,3 @@ fi
%{_libdir}/nemo-transferengine/plugins/sharing/libmastodonshareplugin.so
%{_libdir}/nemo-transferengine/plugins/transfer/libmastodontransferplugin.so
%{_datadir}/nemo-transferengine/plugins/sharing/MastodonShareImage.qml
-
-%files features-all
-# Empty by design.
diff --git a/settings/accounts/providers/mastodon.provider b/settings/accounts/providers/mastodon.provider
index acd9d91..8523691 100644
--- a/settings/accounts/providers/mastodon.provider
+++ b/settings/accounts/providers/mastodon.provider
@@ -3,7 +3,7 @@
<provider version="1.0" id="mastodon">
<name>Mastodon</name>
<description>Mastodon social network</description>
- <icon>image://theme/graphic-service-mastodon</icon>
+ <icon>image://theme/icon-l-mastodon</icon>
<template>
<group name="auth">
diff --git a/settings/accounts/services/mastodon-microblog.service b/settings/accounts/services/mastodon-microblog.service
index 550333e..bbc5945 100644
--- a/settings/accounts/services/mastodon-microblog.service
+++ b/settings/accounts/services/mastodon-microblog.service
@@ -2,11 +2,11 @@
<service id="mastodon-microblog">
<type>microblogging</type>
<name>Posts</name>
- <icon>image://theme/icon-m-events</icon>
+ <icon>image://theme/icon-l-mastodon</icon>
<provider>mastodon</provider>
<template>
- <setting name="sync_profile_templates" type="as">["mastodon.Posts"]</setting>
+ <setting name="sync_profile_templates" type="as">["mastodon.Posts", "mastodon.Notifications"]</setting>
<group name="auth">
<setting name="method">oauth2</setting>
<setting name="mechanism">web_server</setting>
diff --git a/settings/accounts/services/mastodon-sharing.service b/settings/accounts/services/mastodon-sharing.service
index c3ecf37..19e3e24 100644
--- a/settings/accounts/services/mastodon-sharing.service
+++ b/settings/accounts/services/mastodon-sharing.service
@@ -2,7 +2,7 @@
<service id="mastodon-sharing">
<type>sharing</type>
<name>Sharing</name>
- <icon>image://theme/icon-m-share</icon>
+ <icon>image://theme/icon-l-mastodon</icon>
<provider>mastodon</provider>
<template>
diff --git a/transferengine-plugins/mastodonshareplugin/mastodonplugininfo.cpp b/transferengine-plugins/mastodonshareplugin/mastodonplugininfo.cpp
index 405b86e..bc66752 100644
--- a/transferengine-plugins/mastodonshareplugin/mastodonplugininfo.cpp
+++ b/transferengine-plugins/mastodonshareplugin/mastodonplugininfo.cpp
@@ -41,7 +41,7 @@ void MastodonPluginInfo::serviceReady()
info.setAccountId(details.accountId);
info.setMethodId(QLatin1String("Mastodon"));
- info.setMethodIcon(QLatin1String("image://theme/graphic-m-service-mastodon"));
+ info.setMethodIcon(QLatin1String("image://theme/icon-l-mastodon"));
info.setShareUIPath(QLatin1String("/usr/share/nemo-transferengine/plugins/sharing/MastodonShareImage.qml"));
info.setCapabilities(m_capabilities);
diff --git a/transferengine-plugins/mastodontransferplugin/mastodonuploader.cpp b/transferengine-plugins/mastodontransferplugin/mastodonuploader.cpp
index 9e2fa1a..d85e138 100644
--- a/transferengine-plugins/mastodontransferplugin/mastodonuploader.cpp
+++ b/transferengine-plugins/mastodontransferplugin/mastodonuploader.cpp
@@ -30,7 +30,7 @@ QString MastodonUploader::displayName() const
QUrl MastodonUploader::serviceIcon() const
{
- return QUrl(QStringLiteral("image://theme/graphic-s-service-mastodon"));
+ return QUrl(QStringLiteral("image://theme/icon-l-mastodon"));
}
bool MastodonUploader::cancelEnabled() const