[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [choqok/mastodon] microblogs: Add Mastodon support
From:       Andrea Scarpino <null () kde ! org>
Date:       2017-07-31 18:27:44
Message-ID: E1dcFPs-0006oe-Nu () code ! kde ! org
[Download RAW message or body]

Git commit 19c529bdaa74672582fd3e77dbf60c6ceb6f0a03 by Andrea Scarpino.
Committed on 31/07/2017 at 18:27.
Pushed by scarpino into branch 'mastodon'.

Add Mastodon support

M  +1    -0    microblogs/CMakeLists.txt
A  +41   -0    microblogs/mastodon/CMakeLists.txt
A  +18   -0    microblogs/mastodon/choqok_mastodon.desktop
A  +-    --    microblogs/mastodon/icons/128-apps-mastodon_microblog.png
A  +-    --    microblogs/mastodon/icons/16-apps-mastodon_microblog.png
A  +-    --    microblogs/mastodon/icons/22-apps-mastodon_microblog.png
A  +-    --    microblogs/mastodon/icons/32-apps-mastodon_microblog.png
A  +-    --    microblogs/mastodon/icons/48-apps-mastodon_microblog.png
A  +-    --    microblogs/mastodon/icons/64-apps-mastodon_microblog.png
A  +8    -0    microblogs/mastodon/icons/CMakeLists.txt
A  +130  -0    microblogs/mastodon/mastodonaccount.cpp     [License: GPL (v2/3)]
A  +65   -0    microblogs/mastodon/mastodonaccount.h     [License: GPL (v2/3)]
A  +22   -0    microblogs/mastodon/mastodondebug.cpp     [License: LGPL (v2+)]
A  +27   -0    microblogs/mastodon/mastodondebug.h     [License: LGPL (v2+)]
A  +221  -0    microblogs/mastodon/mastodoneditaccountwidget.cpp     [License: GPL \
(v2/3)] A  +61   -0    microblogs/mastodon/mastodoneditaccountwidget.h     [License: \
GPL (v2/3)] A  +225  -0    microblogs/mastodon/mastodoneditaccountwidget.ui
A  +745  -0    microblogs/mastodon/mastodonmicroblog.cpp     [License: GPL (v2/3)]
A  +120  -0    microblogs/mastodon/mastodonmicroblog.h     [License: GPL (v2/3)]
A  +65   -0    microblogs/mastodon/mastodonoauth.cpp     [License: GPL (v2/3)]
A  +55   -0    microblogs/mastodon/mastodonoauth.h     [License: GPL (v2/3)]
A  +38   -0    microblogs/mastodon/mastodonoauthreplyhandler.cpp     [License: GPL \
(v2/3)] A  +39   -0    microblogs/mastodon/mastodonoauthreplyhandler.h     [License: \
GPL (v2/3)] A  +31   -0    microblogs/mastodon/mastodonpost.cpp     [License: GPL \
(v2/3)] A  +38   -0    microblogs/mastodon/mastodonpost.h     [License: GPL (v2/3)]
A  +138  -0    microblogs/mastodon/mastodonpostwidget.cpp     [License: GPL (v2/3)]
A  +56   -0    microblogs/mastodon/mastodonpostwidget.h     [License: GPL (v2/3)]

https://commits.kde.org/choqok/19c529bdaa74672582fd3e77dbf60c6ceb6f0a03

diff --git a/microblogs/CMakeLists.txt b/microblogs/CMakeLists.txt
index fbf153c8..2b4d3750 100644
--- a/microblogs/CMakeLists.txt
+++ b/microblogs/CMakeLists.txt
@@ -1,6 +1,7 @@
 find_package(KF5Attica)
 
 add_subdirectory(twitter)
+add_subdirectory(mastodon)
 add_subdirectory(laconica)
 add_subdirectory(friendica)
 add_subdirectory(pumpio)
diff --git a/microblogs/mastodon/CMakeLists.txt b/microblogs/mastodon/CMakeLists.txt
new file mode 100644
index 00000000..5b6e6139
--- /dev/null
+++ b/microblogs/mastodon/CMakeLists.txt
@@ -0,0 +1,41 @@
+include_directories(
+    ${CHOQOK_INCLUDES}
+)
+
+set(choqok_mastodon_SRCS
+    mastodonaccount.cpp
+    mastodondebug.cpp
+    mastodoneditaccountwidget.cpp
+    mastodonmicroblog.cpp
+    mastodonoauth.cpp
+    mastodonoauthreplyhandler.cpp
+    mastodonpost.cpp
+    mastodonpostwidget.cpp
+)
+
+ki18n_wrap_ui(choqok_mastodon_SRCS
+    mastodoneditaccountwidget.ui
+)
+
+add_library(choqok_mastodon MODULE ${choqok_mastodon_SRCS})
+
+kcoreaddons_desktop_to_json(choqok_mastodon choqok_mastodon.desktop)
+
+target_link_libraries(choqok_mastodon
+PUBLIC
+    Qt5::Core
+    Qt5::Gui
+    Qt5::NetworkAuth
+    Qt5::Widgets
+    KF5::I18n
+    KF5::KIOCore
+    KF5::KIOWidgets
+    KF5::WidgetsAddons
+    qca-qt5
+    choqok
+)
+
+install(TARGETS choqok_mastodon DESTINATION ${PLUGIN_INSTALL_DIR})
+install(FILES choqok_mastodon.desktop DESTINATION ${SERVICES_INSTALL_DIR})
+
+add_subdirectory(icons)
diff --git a/microblogs/mastodon/choqok_mastodon.desktop \
b/microblogs/mastodon/choqok_mastodon.desktop new file mode 100644
index 00000000..341a5f61
--- /dev/null
+++ b/microblogs/mastodon/choqok_mastodon.desktop
@@ -0,0 +1,18 @@
+[Desktop Entry]
+Encoding=UTF-8
+Type=Service
+X-Choqok-Version=1
+Icon=mastodon_microblog
+ServiceTypes=Choqok/Plugin
+X-KDE-Library=choqok_mastodon
+X-KDE-PluginInfo-Author=Andrea Scarpino
+X-KDE-PluginInfo-Email=scarpino@kde.org
+X-KDE-PluginInfo-Name=choqok_mastodon
+X-KDE-PluginInfo-Version=0.1
+X-KDE-PluginInfo-Website=http://choqok.gnufolks.org
+X-KDE-PluginInfo-Category=MicroBlogs
+X-KDE-PluginInfo-Depends=
+X-KDE-PluginInfo-License=GPL
+X-KDE-PluginInfo-EnabledByDefault=false
+Name=Mastodon social
+Comment=Mastodon social
diff --git a/microblogs/mastodon/icons/128-apps-mastodon_microblog.png \
b/microblogs/mastodon/icons/128-apps-mastodon_microblog.png new file mode 100644
index 00000000..2058c3db
Binary files /dev/null and \
                b/microblogs/mastodon/icons/128-apps-mastodon_microblog.png differ
diff --git a/microblogs/mastodon/icons/16-apps-mastodon_microblog.png \
b/microblogs/mastodon/icons/16-apps-mastodon_microblog.png new file mode 100644
index 00000000..6e14a7d3
Binary files /dev/null and b/microblogs/mastodon/icons/16-apps-mastodon_microblog.png \
                differ
diff --git a/microblogs/mastodon/icons/22-apps-mastodon_microblog.png \
b/microblogs/mastodon/icons/22-apps-mastodon_microblog.png new file mode 100644
index 00000000..205ed9b5
Binary files /dev/null and b/microblogs/mastodon/icons/22-apps-mastodon_microblog.png \
                differ
diff --git a/microblogs/mastodon/icons/32-apps-mastodon_microblog.png \
b/microblogs/mastodon/icons/32-apps-mastodon_microblog.png new file mode 100644
index 00000000..abc8b077
Binary files /dev/null and b/microblogs/mastodon/icons/32-apps-mastodon_microblog.png \
                differ
diff --git a/microblogs/mastodon/icons/48-apps-mastodon_microblog.png \
b/microblogs/mastodon/icons/48-apps-mastodon_microblog.png new file mode 100644
index 00000000..7bf4a7a7
Binary files /dev/null and b/microblogs/mastodon/icons/48-apps-mastodon_microblog.png \
                differ
diff --git a/microblogs/mastodon/icons/64-apps-mastodon_microblog.png \
b/microblogs/mastodon/icons/64-apps-mastodon_microblog.png new file mode 100644
index 00000000..20b52395
Binary files /dev/null and b/microblogs/mastodon/icons/64-apps-mastodon_microblog.png \
                differ
diff --git a/microblogs/mastodon/icons/CMakeLists.txt \
b/microblogs/mastodon/icons/CMakeLists.txt new file mode 100644
index 00000000..54b3eafe
--- /dev/null
+++ b/microblogs/mastodon/icons/CMakeLists.txt
@@ -0,0 +1,8 @@
+ecm_install_icons( ICONS
+   16-apps-mastodon_microblog.png
+   22-apps-mastodon_microblog.png
+   32-apps-mastodon_microblog.png
+   48-apps-mastodon_microblog.png
+   64-apps-mastodon_microblog.png
+   128-apps-mastodon_microblog.png
+   DESTINATION ${ICON_INSTALL_DIR} )
diff --git a/microblogs/mastodon/mastodonaccount.cpp \
b/microblogs/mastodon/mastodonaccount.cpp new file mode 100644
index 00000000..e17c69f8
--- /dev/null
+++ b/microblogs/mastodon/mastodonaccount.cpp
@@ -0,0 +1,130 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017  Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+*/
+
+#include "mastodonaccount.h"
+
+#include <KIO/AccessManager>
+
+#include "passwordmanager.h"
+
+#include "mastodonmicroblog.h"
+
+class MastodonAccount::Private
+{
+public:
+    QString consumerKey;
+    QString consumerSecret;
+    QString host;
+    QString acct;
+    QString tokenSecret;
+    QStringList following;
+    QVariantList lists;
+    MastodonOAuth *oAuth;
+    QStringList timelineNames;
+};
+
+MastodonAccount::MastodonAccount(MastodonMicroBlog *parent, const QString &alias):
+    Account(parent, alias), d(new Private)
+{
+    d->host = configGroup()->readEntry("Host", QString());
+    d->acct = configGroup()->readEntry("Acct", QString());
+    d->tokenSecret = \
Choqok::PasswordManager::self()->readPassword(QStringLiteral("%1_tokenSecret").arg(alias));
 +    d->consumerKey = configGroup()->readEntry("ConsumerKey", QString());
+    d->consumerSecret = \
Choqok::PasswordManager::self()->readPassword(QStringLiteral("%1_consumerSecret").arg(alias));
 +    d->oAuth = new MastodonOAuth(this);
+    d->oAuth->setToken(d->tokenSecret);
+
+    setPostCharLimit(500);
+}
+
+MastodonAccount::~MastodonAccount()
+{
+    d->oAuth->deleteLater();
+    delete d;
+}
+
+void MastodonAccount::writeConfig()
+{
+    configGroup()->writeEntry("Host", d->host);
+    configGroup()->writeEntry("Acct", d->acct);
+    configGroup()->writeEntry("ConsumerKey", d->consumerKey);
+    Choqok::PasswordManager::self()->writePassword(QStringLiteral("%1_consumerSecret").arg(alias()),
 +            d->consumerSecret);
+    Choqok::PasswordManager::self()->writePassword(QStringLiteral("%1_tokenSecret").arg(alias()),
 +            d->tokenSecret);
+    Choqok::Account::writeConfig();
+}
+
+QString MastodonAccount::host()
+{
+    return d->host;
+}
+
+void MastodonAccount::setHost(const QString &host)
+{
+    d->host = host;
+}
+
+QString MastodonAccount::acct()
+{
+    return d->acct;
+}
+
+void MastodonAccount::setAcct(const QString &acct)
+{
+    d->acct = acct;
+}
+
+QString MastodonAccount::consumerKey()
+{
+    return d->consumerKey;
+}
+
+void MastodonAccount::setConsumerKey(const QString &consumerKey)
+{
+    d->consumerKey = consumerKey;
+}
+
+QString MastodonAccount::consumerSecret()
+{
+    return d->consumerSecret;
+}
+
+void MastodonAccount::setConsumerSecret(const QString &consumerSecret)
+{
+    d->consumerSecret = consumerSecret;
+}
+
+QString MastodonAccount::tokenSecret()
+{
+    return d->tokenSecret;
+}
+
+void MastodonAccount::setTokenSecret(const QString &tokenSecret)
+{
+    d->tokenSecret = tokenSecret;
+}
+
+MastodonOAuth *MastodonAccount::oAuth()
+{
+    return d->oAuth;
+}
diff --git a/microblogs/mastodon/mastodonaccount.h \
b/microblogs/mastodon/mastodonaccount.h new file mode 100644
index 00000000..05dea4eb
--- /dev/null
+++ b/microblogs/mastodon/mastodonaccount.h
@@ -0,0 +1,65 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017  Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+*/
+
+#ifndef MASTODONACCOUNT_H
+#define MASTODONACCOUNT_H
+
+#include "account.h"
+#include "choqoktypes.h"
+
+#include "mastodonoauth.h"
+
+class MastodonMicroBlog;
+
+class MastodonAccount : public Choqok::Account
+{
+    Q_OBJECT
+public:
+    explicit MastodonAccount(MastodonMicroBlog *parent, const QString &alias);
+    ~MastodonAccount();
+
+    virtual void writeConfig() override;
+
+    QString host();
+    void setHost(const QString &host);
+
+    QString acct();
+    void setAcct(const QString &acct);
+
+    QString consumerKey();
+    void setConsumerKey(const QString &consumerKey);
+
+    QString consumerSecret();
+    void setConsumerSecret(const QString &consumerSecret);
+
+    QString tokenSecret();
+    void setTokenSecret(const QString &tokenSecret);
+
+    MastodonOAuth *oAuth();
+
+private:
+    class Private;
+    Private *d;
+
+};
+
+#endif // MASTODONACCOUNT_H
diff --git a/microblogs/mastodon/mastodondebug.cpp \
b/microblogs/mastodon/mastodondebug.cpp new file mode 100644
index 00000000..1acdbe80
--- /dev/null
+++ b/microblogs/mastodon/mastodondebug.cpp
@@ -0,0 +1,22 @@
+/*  This file is part of the KDE project
+    Copyright (C) 2017 Andrea Scarpino <scarpino@kde.org>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 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
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to
+    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+    Boston, MA 02110-1301, USA.
+*/
+
+#include "mastodondebug.h"
+Q_LOGGING_CATEGORY(CHOQOK, "org.kde.choqok.mastodon")
+
diff --git a/microblogs/mastodon/mastodondebug.h \
b/microblogs/mastodon/mastodondebug.h new file mode 100644
index 00000000..723b95a6
--- /dev/null
+++ b/microblogs/mastodon/mastodondebug.h
@@ -0,0 +1,27 @@
+/*  This file is part of the KDE project
+    Copyright (C) 2017 Andrea Scarpino <scarpino@kde.org>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 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
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to
+    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+    Boston, MA 02110-1301, USA.
+*/
+
+#ifndef MASTODONDEBUG_H
+#define MASTODONDEBUG_H
+
+#include <QLoggingCategory>
+Q_DECLARE_LOGGING_CATEGORY(CHOQOK)
+
+#endif
+
diff --git a/microblogs/mastodon/mastodoneditaccountwidget.cpp \
b/microblogs/mastodon/mastodoneditaccountwidget.cpp new file mode 100644
index 00000000..07072e77
--- /dev/null
+++ b/microblogs/mastodon/mastodoneditaccountwidget.cpp
@@ -0,0 +1,221 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017  Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+*/
+
+#include "mastodoneditaccountwidget.h"
+
+#include <QCoreApplication>
+#include <QCheckBox>
+#include <QInputDialog>
+#include <QJsonDocument>
+#include <QEventLoop>
+#include <QUrl>
+
+#include <KIO/AccessManager>
+#include <KIO/StoredTransferJob>
+#include <KMessageBox>
+
+#include "choqoktools.h"
+#include "accountmanager.h"
+
+#include "mastodonaccount.h"
+#include "mastodondebug.h"
+#include "mastodonmicroblog.h"
+#include "mastodonoauth.h"
+
+MastodonEditAccountWidget::MastodonEditAccountWidget(MastodonMicroBlog *microblog,
+         MastodonAccount *account,
+         QWidget *parent):
+     ChoqokEditAccountWidget(account, parent)
+     , m_account(account)
+{
+    setupUi(this);
+
+    connect(kcfg_authorize, SIGNAL(clicked(bool)), SLOT(authorizeUser()));
+
+    if (m_account) {
+        kcfg_alias->setText(m_account->alias());
+        kcfg_acct->setText(m_account->acct());
+        setAuthenticated(!m_account->tokenSecret().isEmpty());
+    } else {
+        setAuthenticated(false);
+        QString newAccountAlias = microblog->serviceName();
+        const QString servName = newAccountAlias;
+        int counter = 1;
+        while (Choqok::AccountManager::self()->findAccount(newAccountAlias)) {
+            newAccountAlias = QStringLiteral("%1%2").arg(servName).arg(counter);
+            counter++;
+        }
+        m_account = new MastodonAccount(microblog, newAccountAlias);
+        setAccount(m_account);
+        kcfg_alias->setText(newAccountAlias);
+    }
+
+    loadTimelinesTable();
+}
+
+MastodonEditAccountWidget::~MastodonEditAccountWidget()
+{
+}
+
+Choqok::Account *MastodonEditAccountWidget::apply()
+{
+    m_account->setAlias(kcfg_alias->text());
+    m_account->setAcct(kcfg_acct->text());
+    m_account->setTokenSecret(m_account->oAuth()->token());
+    m_account->writeConfig();
+    saveTimelinesTable();
+    return m_account;
+}
+
+void MastodonEditAccountWidget::authorizeUser()
+{
+    qCDebug(CHOQOK);
+    if (kcfg_acct->text().isEmpty() || \
!kcfg_acct->text().contains(QLatin1Char('@'))) { +        return;
+    }
+    if (m_account->consumerKey().isEmpty() || m_account->consumerSecret().isEmpty()) \
{ +        registerClient();
+    }
+
+    connect(m_account->oAuth(), &QAbstractOAuth::authorizeWithBrowser, \
&Choqok::openUrl); +    connect(m_account->oAuth(), &QAbstractOAuth::statusChanged, \
this, &MastodonEditAccountWidget::gotToken); +
+    m_account->oAuth()->grant();
+
+    QString verifier = QInputDialog::getText(this, i18n("coe"),
+                       i18n("Enter the code received from %1", m_account->host()));
+    if (verifier.isEmpty()) {
+        return;
+    }
+
+    m_account->oAuth()->getToken(verifier);
+}
+
+void MastodonEditAccountWidget::gotToken()
+{
+    isAuthenticated = false;
+    if (m_account->oAuth()->status() == QAbstractOAuth::Status::Granted) {
+        setAuthenticated(true);
+        KMessageBox::information(this, i18n("Choqok is authorized successfully."), \
i18n("Authorized")); +    } else {
+        KMessageBox::detailedError(this, i18n("Authorization Error"), i18n("OAuth \
authorization error")); +    }
+}
+
+bool MastodonEditAccountWidget::validateData()
+{
+    if (kcfg_alias->text().isEmpty() || kcfg_acct->text().isEmpty() ||
+            !kcfg_acct->text().contains(QLatin1Char('@')) ||
+            !isAuthenticated) {
+        return false;
+    } else {
+        return true;
+    }
+}
+
+void MastodonEditAccountWidget::setAuthenticated(bool authenticated)
+{
+    isAuthenticated = authenticated;
+    if (authenticated) {
+        kcfg_authorize->setIcon(QIcon::fromTheme(QLatin1String("object-unlocked")));
+        kcfg_authenticateLed->on();
+        kcfg_authenticateStatus->setText(i18n("Authenticated"));
+    } else {
+        kcfg_authorize->setIcon(QIcon::fromTheme(QLatin1String("object-locked")));
+        kcfg_authenticateLed->off();
+        kcfg_authenticateStatus->setText(i18n("Not Authenticated"));
+    }
+}
+
+void MastodonEditAccountWidget::loadTimelinesTable()
+{
+    for (const QString &timeline: m_account->microblog()->timelineNames()) {
+        int newRow = timelinesTable->rowCount();
+        timelinesTable->insertRow(newRow);
+        timelinesTable->setItem(newRow, 0, new QTableWidgetItem(timeline));
+
+        QCheckBox *enable = new QCheckBox(timelinesTable);
+        enable->setChecked(m_account->timelineNames().contains(timeline));
+        timelinesTable->setCellWidget(newRow, 1, enable);
+    }
+}
+
+void MastodonEditAccountWidget::registerClient()
+{
+    if (kcfg_acct->text().contains(QLatin1Char('@'))) {
+        m_account->setUsername(kcfg_acct->text().split(QLatin1Char('@'))[0]);
+        m_account->setHost(QLatin1String("https://") + \
kcfg_acct->text().split(QLatin1Char('@'))[1]); +
+        m_account->oAuth()->setAccessTokenUrl(QUrl(m_account->host() + \
QLatin1String("/oauth/token"))); +        \
m_account->oAuth()->setAuthorizationUrl(QUrl(m_account->host() + \
QLatin1String("/oauth/authorize"))); +
+        QUrl url(m_account->host() + QLatin1String("/api/v1/apps"));
+        QByteArray data;
+        data += "client_name=" + QCoreApplication::applicationName().toLatin1();
+        data += "&redirect_uris=" + \
QUrl::toPercentEncoding(QLatin1String("urn:ietf:wg:oauth:2.0:oob")); +        data += \
"&scopes=" + QUrl::toPercentEncoding(QLatin1String("read write follow")); +        \
data += "&website=" + \
QUrl::toPercentEncoding(QLatin1String("http://choqok.gnufolks.org/")); +
+        KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, \
KIO::HideProgressInfo); +        if (!job) {
+            qCDebug(CHOQOK) << "Cannot create an http POST request!";
+            return;
+        }
+        job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: \
application/x-www-form-urlencoded")); +        QEventLoop loop;
+        connect(job, SIGNAL(result(KJob*)), &loop, SLOT(quit()));
+        job->start();
+        loop.exec();
+
+        if (job->error()) {
+            qCDebug(CHOQOK) << "An error occurred in Job";
+            return;
+        } else {
+            KIO::StoredTransferJob *stj = qobject_cast<KIO::StoredTransferJob \
*>(job); +
+            const QJsonDocument json = QJsonDocument::fromJson(stj->data());
+            if (!json.isNull()) {
+                const QVariantMap result = json.toVariant().toMap();
+                m_account->setConsumerKey(result[QLatin1String("client_id")].toString());
 +                m_account->setConsumerSecret(result[QLatin1String("client_secret")].toString());
 +                m_account->oAuth()->setClientIdentifier(m_account->consumerKey());
+                m_account->oAuth()->setClientIdentifierSharedKey(m_account->consumerSecret());
 +            } else {
+                qCDebug(CHOQOK) << "Cannot parse JSON reply";
+            }
+        }
+    } else {
+        qCDebug(CHOQOK) << "username is not valid";
+    }
+}
+
+void MastodonEditAccountWidget::saveTimelinesTable()
+{
+    QStringList timelines;
+    for (int i = 0; i < timelinesTable->rowCount(); ++i) {
+        QCheckBox *enable = qobject_cast<QCheckBox *>(timelinesTable->cellWidget(i, \
1)); +        if (enable && enable->isChecked()) {
+            timelines.append(timelinesTable->item(i, 0)->text());
+        }
+    }
+    //m_account->setTimelineNames(timelines);
+}
diff --git a/microblogs/mastodon/mastodoneditaccountwidget.h \
b/microblogs/mastodon/mastodoneditaccountwidget.h new file mode 100644
index 00000000..aa1d8e73
--- /dev/null
+++ b/microblogs/mastodon/mastodoneditaccountwidget.h
@@ -0,0 +1,61 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017  Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+*/
+
+#ifndef MASTODONEDITACCOUNTWIDGET_H
+#define MASTODONEDITACCOUNTWIDGET_H
+
+#include "editaccountwidget.h"
+
+#include <QUrlQuery>
+
+#include "ui_mastodoneditaccountwidget.h"
+
+class MastodonAccount;
+class MastodonMicroBlog;
+
+class MastodonEditAccountWidget : public ChoqokEditAccountWidget, \
Ui::MastodonEditAccountWidget +{
+    Q_OBJECT
+public:
+    explicit MastodonEditAccountWidget(MastodonMicroBlog *microblog, MastodonAccount \
*account, +                                       QWidget *parent);
+    ~MastodonEditAccountWidget();
+
+    virtual Choqok::Account *apply() override;
+
+    virtual bool validateData() override;
+
+private Q_SLOTS:
+    void authorizeUser();
+    void gotToken();
+
+private:
+    void setAuthenticated(bool authenticated);
+    void loadTimelinesTable();
+    void registerClient();
+    void saveTimelinesTable();
+
+    MastodonAccount *m_account;
+    bool isAuthenticated;
+};
+
+#endif // MASTODONEDITACCOUNTWIDGET_H
diff --git a/microblogs/mastodon/mastodoneditaccountwidget.ui \
b/microblogs/mastodon/mastodoneditaccountwidget.ui new file mode 100644
index 00000000..819fc07f
--- /dev/null
+++ b/microblogs/mastodon/mastodoneditaccountwidget.ui
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MastodonEditAccountWidget</class>
+ <widget class="QWidget" name="MastodonEditAccountWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>361</width>
+    <height>403</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QTabWidget" name="tabwidget">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="tabAccount">
+      <attribute name="title">
+       <string>Mastodon Account</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout">
+       <item row="0" column="0">
+        <widget class="QLabel" name="label_3">
+         <property name="text">
+          <string>A&amp;lias:</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+         <property name="buddy">
+          <cstring>kcfg_alias</cstring>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="1">
+        <widget class="QLineEdit" name="kcfg_alias">
+         <property name="toolTip">
+          <string>The account alias</string>
+         </property>
+         <property name="whatsThis">
+          <string>The alias is the name you want to give to your account. It should \
be unique. You can have several connections to the same service so the alias lets you \
give them names.</string> +         </property>
+        </widget>
+       </item>
+       <item row="1" column="1">
+        <widget class="QLabel" name="label">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text">
+          <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; \
&quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt; \
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; \
/&gt;&lt;style type=&quot;text/css&quot;&gt; +p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans'; \
font-size:9pt; font-weight:400; font-style:normal;&quot;&gt; +&lt;p style=&quot; \
margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; \
-qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt; \
font-weight:600;&quot;&gt;Note:&lt;/span&gt;&lt;span style=&quot; \
font-size:8pt;&quot;&gt; The alias must be \
unique.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> +         \
</property> +         <property name="alignment">
+          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="0" colspan="2">
+        <widget class="QGroupBox" name="kcfg_basicAuth">
+         <property name="title">
+          <string>Credentials</string>
+         </property>
+         <layout class="QFormLayout" name="formLayout">
+          <property name="fieldGrowthPolicy">
+           <enum>QFormLayout::ExpandingFieldsGrow</enum>
+          </property>
+          <item row="0" column="0">
+           <widget class="QLabel" name="label_4">
+            <property name="text">
+             <string>username@domain:</string>
+            </property>
+            <property name="alignment">
+             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <widget class="QLineEdit" name="kcfg_acct"/>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item row="3" column="0" colspan="2">
+        <widget class="QGroupBox" name="kcfg_credentialsBox">
+         <property name="title">
+          <string>OAuth Authentication</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout_2">
+          <item row="0" column="0" colspan="2">
+           <widget class="QLabel" name="label_2">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string>Click the below button; if everything goes well, you will \
pointed to Mastodon website to allow access to Choqok.</string> +            \
</property> +            <property name="wordWrap">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0">
+           <widget class="KLed" name="kcfg_authenticateLed">
+            <property name="enabled">
+             <bool>true</bool>
+            </property>
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="state">
+             <enum>KLed::Off</enum>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QLabel" name="kcfg_authenticateStatus">
+            <property name="text">
+             <string>Not Authenticated</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="0" colspan="2">
+           <widget class="QPushButton" name="kcfg_authorize">
+            <property name="toolTip">
+             <string>Verify credentials</string>
+            </property>
+            <property name="text">
+             <string>&amp;Authenticate with Mastodon Service</string>
+            </property>
+            <property name="icon">
+             <iconset theme="object-locked">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item row="4" column="0" colspan="2">
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab">
+      <attribute name="title">
+       <string>Timelines Configuration</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <item>
+        <widget class="QLabel" name="label_5">
+         <property name="font">
+          <font>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text">
+          <string>Which timelines do you like to be enabled?</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QTableWidget" name="timelinesTable">
+         <column>
+          <property name="text">
+           <string>Name</string>
+          </property>
+         </column>
+         <column>
+          <property name="text">
+           <string>Enable</string>
+          </property>
+         </column>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>KLed</class>
+   <extends>QWidget</extends>
+   <header>kled.h</header>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>kcfg_alias</tabstop>
+  <tabstop>kcfg_acct</tabstop>
+  <tabstop>kcfg_authorize</tabstop>
+  <tabstop>tabwidget</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/microblogs/mastodon/mastodonmicroblog.cpp \
b/microblogs/mastodon/mastodonmicroblog.cpp new file mode 100644
index 00000000..93da4db6
--- /dev/null
+++ b/microblogs/mastodon/mastodonmicroblog.cpp
@@ -0,0 +1,745 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017 Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+*/
+
+#include "mastodonmicroblog.h"
+
+#include <QAction>
+#include <QFile>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QMenu>
+#include <QMimeDatabase>
+#include <QTextDocument>
+
+#include <KIO/StoredTransferJob>
+#include <KPluginFactory>
+
+#include "accountmanager.h"
+#include "application.h"
+#include "choqokbehaviorsettings.h"
+#include "notifymanager.h"
+#include "postwidget.h"
+
+#include "mastodonaccount.h"
+#include "mastodondebug.h"
+#include "mastodoneditaccountwidget.h"
+#include "mastodonpost.h"
+#include "mastodonpostwidget.h"
+
+class MastodonMicroBlog::Private
+{
+public:
+    Private(): countOfTimelinesToSave(0)
+    {}
+    int countOfTimelinesToSave;
+};
+
+K_PLUGIN_FACTORY_WITH_JSON(MastodonMicroBlogFactory, "choqok_mastodon.json",
+                           registerPlugin < MastodonMicroBlog > ();)
+
+const QString MastodonMicroBlog::homeTimeline(QLatin1String("/api/v1/timelines/home"));
 +const QString MastodonMicroBlog::publicTimeline(QLatin1String("/api/v1/timelines/public"));
 +
+MastodonMicroBlog::MastodonMicroBlog(QObject *parent, const QVariantList &args):
+    MicroBlog(QStringLiteral("Mastodon") , parent), d(new Private)
+{
+    Q_UNUSED(args)
+    setServiceName(QLatin1String("Mastodon"));
+    setServiceHomepageUrl(QLatin1String("https://mastodon.social"));
+    QStringList timelineNames;
+    timelineNames << QLatin1String("Home") << QLatin1String("Local") << \
QLatin1String("Federated"); +    setTimelineNames(timelineNames);
+    setTimelinesInfo();
+}
+
+MastodonMicroBlog::~MastodonMicroBlog()
+{
+    qDeleteAll(m_timelinesInfos);
+    delete d;
+}
+
+void MastodonMicroBlog::aboutToUnload()
+{
+    for (Choqok::Account *acc: Choqok::AccountManager::self()->accounts()) {
+        if (acc->microblog() == this) {
+            d->countOfTimelinesToSave += acc->timelineNames().count();
+        }
+    }
+    Q_EMIT saveTimelines();
+}
+
+ChoqokEditAccountWidget *MastodonMicroBlog::createEditAccountWidget(Choqok::Account \
*account, +        QWidget *parent)
+{
+    MastodonAccount *acc = qobject_cast<MastodonAccount * >(account);
+    if (acc || !account) {
+        return new MastodonEditAccountWidget(this, acc, parent);
+    } else {
+        qCDebug(CHOQOK) << "Account passed here was not a valid MastodonAccount!";
+        return 0;
+    }
+}
+
+void MastodonMicroBlog::createPost(Choqok::Account *theAccount, Choqok::Post *post)
+{
+    if (!post || post->content.isEmpty()) {
+        qCDebug(CHOQOK) << "ERROR: Status text is empty!";
+        Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::OtherError,
+                         i18n("Creating the new post failed. Text is empty."), \
MicroBlog::Critical); +        return;
+    }
+
+    MastodonAccount *acc = qobject_cast<MastodonAccount *>(theAccount);
+    if (acc) {
+        QVariantMap object;
+        object.insert(QLatin1String("status"), post->content);
+
+        const QByteArray data = QJsonDocument::fromVariant(object).toJson();
+
+        QUrl url(acc->host());
+        url = url.adjusted(QUrl::StripTrailingSlash);
+        url.setPath(url.path() + QLatin1String("/api/v1/statuses"));
+        KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, \
KIO::HideProgressInfo); +        job->addMetaData(QLatin1String("content-type"), \
QLatin1String("Content-Type: application/json")); +        \
job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(acc)); +    \
if (!job) { +            qCDebug(CHOQOK) << "Cannot create an http POST request!";
+            return;
+        }
+        m_accountJobs[job] = acc;
+        m_createPostJobs[job] = post;
+        connect(job, SIGNAL(result(KJob*)), this, SLOT(slotCreatePost(KJob*)));
+        job->start();
+    } else {
+        qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!";
+    }
+}
+
+Choqok::Account *MastodonMicroBlog::createNewAccount(const QString &alias)
+{
+    MastodonAccount *acc = qobject_cast<MastodonAccount *>(
+                             Choqok::AccountManager::self()->findAccount(alias));
+    if (!acc) {
+        return new MastodonAccount(this, alias);
+    } else {
+        qCDebug(CHOQOK) << "Cannot create a new MastodonAccount!";
+        return 0;
+    }
+}
+
+QString MastodonMicroBlog::lastTimelineId(Choqok::Account *theAccount,
+                                          const QString &timeline) const
+{
+    qCDebug(CHOQOK) << "Latest ID for timeline " << timeline << \
m_timelinesLatestIds[theAccount][timeline]; +    return \
m_timelinesLatestIds[theAccount][timeline]; +}
+
+QList< Choqok::Post * > MastodonMicroBlog::readTimeline(const QByteArray &buffer)
+{
+    QList<Choqok::Post * > posts;
+    const QJsonDocument json = QJsonDocument::fromJson(buffer);
+    if (!json.isNull()) {
+        const QVariantList list = json.array().toVariantList();
+        for (const QVariant &element: list) {
+            posts.prepend(readPost(element.toMap(), new MastodonPost));
+        }
+    } else {
+        qCDebug(CHOQOK) << "Cannot parse JSON reply";
+    }
+
+    return posts;
+}
+
+Choqok::Post *MastodonMicroBlog::readPost(const QVariantMap &var, Choqok::Post \
*post) +{
+    MastodonPost *p = dynamic_cast< MastodonPost * >(post);
+    if (p) {
+        QVariantMap reblog = var[QLatin1String("reblog")].toMap();
+        QVariantMap status;
+        if (reblog.isEmpty()) {
+            status = var;
+        } else {
+            status = reblog;
+        }
+
+        QTextDocument content;
+        content.setHtml(status[QLatin1String("spoiler_text")].toString() + \
QLatin1String("<br />") + status[QLatin1String("content")].toString()); +        \
p->content += content.toPlainText().trimmed(); +
+        p->creationDateTime = \
QDateTime::fromString(var[QLatin1String("created_at")].toString(), +                  \
Qt::ISODate); +        p->creationDateTime.setTimeSpec(Qt::UTC);
+
+        p->link = var[QLatin1String("url")].toString();
+        p->isFavorited = var[QLatin1String("favourited")].toBool();
+        if (p->isFavorited) {
+            p->isRead = true;
+        }
+        p->postId = var[QLatin1String("id")].toString();
+
+        p->conversationId = var[QLatin1String("id")].toString();
+
+        QVariantMap application = var[QLatin1String("application")].toMap();
+        if (!application.isEmpty()) {
+            p->source = application[QLatin1String("name")].toString();
+        }
+
+        if (var[QLatin1String("visibility")].toString().compare(QLatin1String("direct")) \
== 0) { +            p->isPrivate = true;
+        }
+
+        QVariantMap account = status[QLatin1Literal("account")].toMap();
+
+        p->author.userId = account[QLatin1String("acct")].toString();
+        p->author.userName = account[QLatin1String("username")].toString();
+        p->author.realName = account[QLatin1String("display_name")].toString();
+        p->author.homePageUrl = account[QLatin1String("url")].toString();
+        p->author.followersCount = \
account[QLatin1String("followers_count")].toUInt(); +
+        QTextDocument description;
+        description.setHtml(account[QLatin1String("note")].toString());
+        p->author.description = description.toPlainText().trimmed();
+
+        p->author.profileImageUrl = account[QLatin1String("avatar")].toString();
+
+        p->replyToPostId = var[QLatin1String("in_reply_to_id")].toString();
+        p->replyToUserId = var[QLatin1String("in_reply_to_account_id")].toString();
+
+        if (!reblog.isEmpty()) {
+            p->repeatedDateTime = \
QDateTime::fromString(var[QLatin1String("created_at")].toString(), +                  \
Qt::ISODate); +            p->repeatedDateTime.setTimeSpec(Qt::UTC);
+
+            p->repeatedPostId = var[QLatin1String("id")].toString();
+            p->repeatedFromUsername = \
var[QLatin1Literal("account")].toMap()[QLatin1String("acct")].toString(); +        }
+
+        return p;
+    } else {
+        qCDebug(CHOQOK) << "post is not a MastodonPost!";
+        return post;
+    }
+}
+
+void MastodonMicroBlog::toggleReblog(Choqok::Account *theAccount, Choqok::Post \
*post) +{
+    MastodonAccount *acc = qobject_cast<MastodonAccount *>(theAccount);
+    if (acc) {
+        QUrl url(acc->host());
+        url = url.adjusted(QUrl::StripTrailingSlash);
+        if (acc->username().compare(post->repeatedFromUsername) == 0) {
+            url.setPath(url.path() + \
QStringLiteral("/api/v1/statuses/%1/unreblog").arg(post->postId)); +        } else {
+            url.setPath(url.path() + \
QStringLiteral("/api/v1/statuses/%1/reblog").arg(post->postId)); +        }
+        KIO::StoredTransferJob *job = KIO::storedHttpPost(QByteArray(), url, \
KIO::HideProgressInfo); +        job->addMetaData(QLatin1String("content-type"), \
QLatin1String("Content-Type: application/json")); +        \
job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(acc)); +    \
if (!job) { +            qCDebug(CHOQOK) << "Cannot create an http POST request!";
+            return;
+        }
+        m_accountJobs[job] = acc;
+        m_shareJobs[job] = post;
+        connect(job, SIGNAL(result(KJob*)), this, SLOT(slotReblog(KJob*)));
+        job->start();
+    } else {
+        qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!";
+    }
+}
+
+void MastodonMicroBlog::slotReblog(KJob *job)
+{
+    qCDebug(CHOQOK);
+    if (!job) {
+        qCDebug(CHOQOK) << "Job is null pointer";
+        return;
+    }
+    Choqok::Post *post = m_shareJobs.take(job);
+    Choqok::Account *theAccount = m_accountJobs.take(job);
+    if (!post || !theAccount) {
+        qCDebug(CHOQOK) << "Account or Post is NULL pointer";
+        return;
+    }
+    int ret = 1;
+    if (job->error()) {
+        qCDebug(CHOQOK) << "Job Error:" << job->errorString();
+    } else {
+        Choqok::UI::Global::mainWindow()->showStatusMessage(
+            i18n("The post has been shared."));
+        KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
+
+        const QJsonDocument json = QJsonDocument::fromJson(j->data());
+        if (!json.isNull()) {
+            ret = 0;
+        } else {
+            qCDebug(CHOQOK) << "Cannot parse JSON reply";
+        }
+    }
+
+    if (ret) {
+        Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError,
+                     i18n("Cannot share the post. %1", job->errorString()));
+    }
+}
+
+void MastodonMicroBlog::toggleFavorite(Choqok::Account *theAccount, Choqok::Post \
*post) +{
+    MastodonAccount *acc = qobject_cast<MastodonAccount *>(theAccount);
+    if (acc) {
+        QUrl url(acc->host());
+        url = url.adjusted(QUrl::StripTrailingSlash);
+
+        if (post->isFavorited) {
+            url.setPath(url.path() + \
QStringLiteral("/api/v1/statuses/%1/unfavourite").arg(post->postId)); +        } else \
{ +            url.setPath(url.path() + \
QStringLiteral("/api/v1/statuses/%1/favourite").arg(post->postId)); +        }
+
+        KIO::StoredTransferJob *job = KIO::storedHttpPost(QByteArray(), url, \
KIO::HideProgressInfo); +        job->addMetaData(QLatin1String("content-type"), \
QLatin1String("Content-Type: application/json")); +        \
job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(acc)); +    \
if (!job) { +            qCDebug(CHOQOK) << "Cannot create an http POST request!";
+            return;
+        }
+        m_accountJobs[job] = acc;
+        m_favoriteJobs[job] = post;
+        connect(job, SIGNAL(result(KJob*)), this, SLOT(slotFavorite(KJob*)));
+        job->start();
+    } else {
+        qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!";
+    }
+}
+
+void MastodonMicroBlog::slotFavorite(KJob *job)
+{
+    qCDebug(CHOQOK);
+    if (!job) {
+        qCDebug(CHOQOK) << "Job is null pointer";
+        return;
+    }
+    Choqok::Post *post = m_favoriteJobs.take(job);
+    Choqok::Account *theAccount = m_accountJobs.take(job);
+    if (!post || !theAccount) {
+        qCDebug(CHOQOK) << "Account or Post is NULL pointer";
+        return;
+    }
+    if (job->error()) {
+        qCDebug(CHOQOK) << "Job Error:" << job->errorString();
+        Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError,
+                     i18n("Cannot set/unset the post as favorite. %1", \
job->errorString())); +    } else {
+        post->isFavorited = !post->isFavorited;
+        Q_EMIT favorite(theAccount, post);
+    }
+}
+
+void MastodonMicroBlog::setLastTimelineId(Choqok::Account *theAccount,
+                                          const QString &timeline,
+                                          const QString &id)
+{
+    m_timelinesLatestIds[theAccount][timeline] = id;
+}
+
+void MastodonMicroBlog::setTimelinesInfo()
+{
+    Choqok::TimelineInfo *t = new Choqok::TimelineInfo;
+    t->name = i18nc("Timeline Name", "Home");
+    t->description = i18nc("Timeline description", "You and people you follow");
+    t->icon = QLatin1String("user-home");
+    m_timelinesInfos[QLatin1String("Home")] = t;
+    m_timelinesPaths[QLatin1String("Home")] = homeTimeline;
+
+    t = new Choqok::TimelineInfo;
+    t->name = i18nc("Timeline Name", "Local");
+    t->description = i18nc("Timeline description", "Local timeline");
+    t->icon = QLatin1String("folder-public");
+    m_timelinesInfos[QLatin1String("Local")] = t;
+    m_timelinesPaths[QLatin1String("Local")] = publicTimeline;
+
+    t = new Choqok::TimelineInfo;
+    t->name = i18nc("Timeline Name", "Federated");
+    t->description = i18nc("Timeline description", "Federated timelime");
+    t->icon = QLatin1String("folder-remote");
+    m_timelinesInfos[QLatin1String("Federated")] = t;
+    m_timelinesPaths[QLatin1String("Federated")] = publicTimeline;
+}
+
+void MastodonMicroBlog::removePost(Choqok::Account *theAccount, Choqok::Post *post)
+{
+    MastodonAccount *acc = qobject_cast<MastodonAccount *>(theAccount);
+    if (acc) {
+        QUrl url(acc->host());
+        url = url.adjusted(QUrl::StripTrailingSlash);
+        url.setPath(url.path() + \
QStringLiteral("/api/v1/statuses/%1").arg(post->postId)); +        KIO::TransferJob \
*job = KIO::http_delete(url, KIO::HideProgressInfo); +        \
job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: \
application/json")); +        job->addMetaData(QLatin1String("customHTTPHeader"), \
authorizationMetaData(acc)); +        if (!job) {
+            qCDebug(CHOQOK) << "Cannot create an http POST request!";
+            return;
+        }
+        m_accountJobs[job] = acc;
+        m_removePostJobs[job] = post;
+        connect(job, SIGNAL(result(KJob*)), this, SLOT(slotRemovePost(KJob*)));
+        job->start();
+    } else {
+        qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!";
+    }
+}
+
+QList<Choqok::Post * > MastodonMicroBlog::loadTimeline(Choqok::Account *account,
+                                                const QString &timelineName)
+{
+    QList< Choqok::Post * > list;
+    const QString fileName = \
Choqok::AccountManager::generatePostBackupFileName(account->alias(), +                \
timelineName); +    const KConfig postsBackup(fileName, KConfig::NoGlobals, \
QStandardPaths::DataLocation); +    const QStringList tmpList = \
postsBackup.groupList(); +
+    // don't load old archives
+    if (tmpList.isEmpty() || !(QDateTime::fromString(tmpList.first()).isValid())) {
+        return list;
+    }
+
+    QList<QDateTime> groupList;
+    for (const QString &str: tmpList) {
+        groupList.append(QDateTime::fromString(str));
+    }
+    qSort(groupList);
+    MastodonPost *st;
+    for (const QDateTime &datetime: groupList) {
+        st = new MastodonPost;
+        KConfigGroup grp(&postsBackup, datetime.toString());
+        st->creationDateTime = grp.readEntry("creationDateTime", \
QDateTime::currentDateTime()); +        st->postId = grp.readEntry("postId", \
QString()); +        st->link = grp.readEntry("link", QString());
+        st->content = grp.readEntry("content", QString());
+        st->source = grp.readEntry("source", QString());
+        st->isFavorited = grp.readEntry("favorited", false);
+        st->author.userId = grp.readEntry("authorId", QString());
+        st->author.userName = grp.readEntry("authorUserName", QString());
+        st->author.realName = grp.readEntry("authorRealName", QString());
+        st->author.description = grp.readEntry("authorDescription" , QString());
+        st->author.profileImageUrl = grp.readEntry("authorProfileImageUrl", \
QString()); +        st->author.homePageUrl = grp.readEntry("authorHomePageUrl", \
QString()); +        st->isRead = grp.readEntry("isRead", true);
+        st->conversationId = grp.readEntry("conversationId", QString());
+        st->replyToPostId = grp.readEntry("replyToPostId", QString());
+        st->replyToUserId = grp.readEntry("replyToUserId", QString());
+        st->repeatedFromUsername = grp.readEntry("repeatedFrom", QString());
+        st->repeatedPostId = grp.readEntry("repeatedPostId", QString());
+        st->repeatedDateTime = grp.readEntry("repeatedDateTime", QDateTime());
+
+        list.append(st);
+    }
+
+    if (!list.isEmpty()) {
+        setLastTimelineId(account, timelineName, list.last()->conversationId);
+    }
+
+    return list;
+}
+
+QUrl MastodonMicroBlog::profileUrl(Choqok::Account *account, const Choqok::User \
&user) const +{
+    Q_UNUSED(account)
+    return QUrl(user.homePageUrl);
+}
+
+QString MastodonMicroBlog::hostFromAcct(const QString &acct)
+{
+    if (acct.contains(QLatin1Char('@'))) {
+        return acct.split(QLatin1Char('@'))[1];
+    } else {
+        return acct;
+    }
+}
+
+QString MastodonMicroBlog::userNameFromAcct(const QString &acct)
+{
+    if (acct.contains(QLatin1Char('@'))) {
+        return acct.split(QLatin1Char('@'))[0];
+    } else {
+        return acct;
+    }
+}
+
+void MastodonMicroBlog::saveTimeline(Choqok::Account *account, const QString \
&timelineName, +                                     const QList< \
Choqok::UI::PostWidget * > &timeline) +{
+    const QString fileName = \
Choqok::AccountManager::generatePostBackupFileName(account->alias(), +                \
timelineName); +    KConfig postsBackup(fileName, KConfig::NoGlobals, \
QStandardPaths::DataLocation); +
+    ///Clear previous data:
+    for (const QString &group: postsBackup.groupList()) {
+        postsBackup.deleteGroup(group);
+    }
+
+    for (Choqok::UI::PostWidget *wd: timeline) {
+        MastodonPost *post = dynamic_cast<MastodonPost * >(wd->currentPost());
+        KConfigGroup grp(&postsBackup, post->creationDateTime.toString());
+        grp.writeEntry("creationDateTime", post->creationDateTime);
+        grp.writeEntry("postId", post->postId);
+        grp.writeEntry("link", post->link);
+        grp.writeEntry("content", post->content);
+        grp.writeEntry("source", post->source);
+        grp.writeEntry("favorited", post->isFavorited);
+        grp.writeEntry("authorId", post->author.userId);
+        grp.writeEntry("authorRealName", post->author.realName);
+        grp.writeEntry("authorUserName", post->author.userName);
+        grp.writeEntry("authorDescription", post->author.description);
+        grp.writeEntry("authorProfileImageUrl", post->author.profileImageUrl);
+        grp.writeEntry("authorHomePageUrl", post->author.homePageUrl);
+        grp.writeEntry("isRead", post->isRead);
+        grp.writeEntry("conversationId", post->conversationId);
+        grp.writeEntry("replyToPostId", post->replyToPostId);
+        grp.writeEntry("replyToUserId", post->replyToUserId);
+        grp.writeEntry("repeatedFrom", post->repeatedFromUsername);
+        grp.writeEntry("repeatedPostId", post->repeatedPostId);
+        grp.writeEntry("repeatedDateTime", post->repeatedDateTime);
+    }
+    postsBackup.sync();
+
+    if (Choqok::Application::isShuttingDown()) {
+        --d->countOfTimelinesToSave;
+        if (d->countOfTimelinesToSave < 1) {
+            Q_EMIT readyForUnload();
+        }
+    }
+}
+
+Choqok::TimelineInfo *MastodonMicroBlog::timelineInfo(const QString &timelineName)
+{
+    return m_timelinesInfos.value(timelineName);
+}
+
+void MastodonMicroBlog::updateTimelines(Choqok::Account *theAccount)
+{
+    MastodonAccount *acc = qobject_cast<MastodonAccount *>(theAccount);
+    if (acc) {
+        for (const QString &timeline: acc->timelineNames()) {
+            QUrl url(acc->host());
+            url = url.adjusted(QUrl::StripTrailingSlash);
+            url.setPath(url.path() + QLatin1Char('/') + m_timelinesPaths[timeline]);
+
+            QUrlQuery query;
+            if (timeline.compare(QLatin1String("Local")) == 0) {
+                query.addQueryItem(QLatin1String("local"), QLatin1String("true"));
+            }
+            url.setQuery(query);
+
+            KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, \
KIO::HideProgressInfo); +            if (!job) {
+                qCDebug(CHOQOK) << "Cannot create an http GET request!";
+                continue;
+            }
+            job->addMetaData(QLatin1String("customHTTPHeader"), \
authorizationMetaData(acc)); +            m_timelinesRequests[job] = timeline;
+            m_accountJobs[job] = acc;
+            connect(job, SIGNAL(result(KJob*)), this, \
SLOT(slotUpdateTimeline(KJob*))); +            job->start();
+        }
+    } else {
+        qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!";
+    }
+}
+
+QString MastodonMicroBlog::authorizationMetaData(MastodonAccount *account) const
+{
+    return QStringLiteral("Authorization: Bearer ") + account->oAuth()->token();
+}
+
+Choqok::UI::PostWidget *MastodonMicroBlog::createPostWidget(Choqok::Account \
*account, +        Choqok::Post *post,
+        QWidget *parent)
+{
+    return new MastodonPostWidget(account, post, parent);
+}
+
+void MastodonMicroBlog::fetchPost(Choqok::Account *theAccount, Choqok::Post *post)
+{
+    MastodonAccount *acc = qobject_cast<MastodonAccount *>(theAccount);
+    if (acc) {
+        if (!post->link.startsWith(acc->host())) {
+            qCDebug(CHOQOK) << "You can only fetch posts from your host!";
+            return;
+        }
+        QUrl url(post->link);
+
+        KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, \
KIO::HideProgressInfo); +        if (!job) {
+            qCDebug(CHOQOK) << "Cannot create an http GET request!";
+            return;
+        }
+        job->addMetaData(QLatin1String("customHTTPHeader"), \
authorizationMetaData(acc)); +        m_accountJobs[job] = acc;
+        connect(job, SIGNAL(result(KJob*)), this, SLOT(slotFetchPost(KJob*)));
+        job->start();
+    } else {
+        qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!";
+    }
+}
+
+void MastodonMicroBlog::slotCreatePost(KJob *job)
+{
+    qCDebug(CHOQOK);
+    if (!job) {
+        qCDebug(CHOQOK) << "Job is null pointer";
+        return;
+    }
+    Choqok::Post *post = m_createPostJobs.take(job);
+    Choqok::Account *theAccount = m_accountJobs.take(job);
+    if (!post || !theAccount) {
+        qCDebug(CHOQOK) << "Account or Post is NULL pointer";
+        return;
+    }
+    int ret = 1;
+    if (job->error()) {
+        qCDebug(CHOQOK) << "Job Error:" << job->errorString();
+    } else {
+        KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
+
+        const QJsonDocument json = QJsonDocument::fromJson(j->data());
+        if (!json.isNull()) {
+            const QVariantMap reply = json.toVariant().toMap();
+            if (!reply[QLatin1String("id")].toString().isEmpty()) {
+                Choqok::NotifyManager::success(i18n("New post submitted \
successfully")); +                ret = 0;
+                Q_EMIT postCreated(theAccount, post);
+            }
+        } else {
+            qCDebug(CHOQOK) << "Cannot parse JSON reply";
+        }
+    }
+
+    if (ret) {
+        Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::CommunicationError,
+                         i18n("Creating the new post failed. %1", \
job->errorString()), +                         MicroBlog::Critical);
+    }
+}
+
+void MastodonMicroBlog::slotFetchPost(KJob *job)
+{
+    qCDebug(CHOQOK);
+    if (!job) {
+        qCDebug(CHOQOK) << "Job is null pointer";
+        return;
+    }
+    Choqok::Account *theAccount = m_accountJobs.take(job);
+    if (!theAccount) {
+        qCDebug(CHOQOK) << "Account or postId is NULL pointer";
+        return;
+    }
+    int ret = 1;
+    if (job->error()) {
+        qCDebug(CHOQOK) << "Job Error:" << job->errorString();
+    } else {
+        KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
+
+        const QJsonDocument json = QJsonDocument::fromJson(j->data());
+        if (!json.isNull()) {
+            const QVariantMap reply = json.toVariant().toMap();
+            MastodonPost *post = new MastodonPost;
+            readPost(reply, post);
+            ret = 0;
+            Q_EMIT postFetched(theAccount, post);
+        } else {
+            qCDebug(CHOQOK) << "Cannot parse JSON reply";
+        }
+    }
+
+    if (ret) {
+        Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError,
+                     i18n("Cannot fetch post. %1", job->errorString()),
+                     MicroBlog::Critical);
+    }
+}
+
+void MastodonMicroBlog::slotRemovePost(KJob *job)
+{
+    qCDebug(CHOQOK);
+    if (!job) {
+        qCDebug(CHOQOK) << "Job is null pointer";
+        return;
+    }
+    Choqok::Post *post = m_removePostJobs.take(job);
+    Choqok::Account *theAccount = m_accountJobs.take(job);
+    if (!post || !theAccount) {
+        qCDebug(CHOQOK) << "Account or Post is NULL pointer";
+        return;
+    }
+    int ret = 1;
+    if (job->error()) {
+        qCDebug(CHOQOK) << "Job Error:" << job->errorString();
+    } else {
+        KIO::TransferJob *j = qobject_cast<KIO::TransferJob * >(job);
+
+        if (j->metaData().contains(QStringLiteral("responsecode"))) {
+            int responseCode = \
j->queryMetaData(QStringLiteral("responsecode")).toInt(); +
+            if (responseCode == 200 || responseCode == 404) {
+                ret = 0;
+                Q_EMIT postRemoved(theAccount, post);
+            }
+        }
+    }
+
+    if (ret) {
+        Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::CommunicationError,
+                         i18n("Removing the post failed. %1", job->errorString()),
+                         MicroBlog::Critical);
+    }
+}
+
+void MastodonMicroBlog::slotUpdateTimeline(KJob *job)
+{
+    qCDebug(CHOQOK);
+    if (!job) {
+        qCDebug(CHOQOK) << "Job is null pointer";
+        return;
+    }
+    Choqok::Account *account = m_accountJobs.take(job);
+    if (!account) {
+        qCDebug(CHOQOK) << "Account or Post is NULL pointer";
+        return;
+    }
+    if (job->error()) {
+        qCDebug(CHOQOK) << "Job Error:" << job->errorString();
+        Q_EMIT error(account, Choqok::MicroBlog::CommunicationError,
+                     i18n("An error occurred when fetching the timeline"));
+    } else {
+        KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
+        const QList<Choqok::Post * > list = readTimeline(j->data());
+        const QString timeline(m_timelinesRequests.take(job));
+        if (!list.isEmpty()) {
+            setLastTimelineId(account, timeline, list.last()->conversationId);
+        }
+
+        Q_EMIT timelineDataReceived(account, timeline, list);
+    }
+}
+
+#include "mastodonmicroblog.moc"
diff --git a/microblogs/mastodon/mastodonmicroblog.h \
b/microblogs/mastodon/mastodonmicroblog.h new file mode 100644
index 00000000..3f489e52
--- /dev/null
+++ b/microblogs/mastodon/mastodonmicroblog.h
@@ -0,0 +1,120 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017 Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+*/
+
+#ifndef MASTODONMICROBLOG_H
+#define MASTODONMICROBLOG_H
+
+#include <QNetworkAccessManager>
+
+#include "microblog.h"
+
+class QUrl;
+class KJob;
+class MastodonAccount;
+class MastodonPost;
+
+class MastodonMicroBlog : public Choqok::MicroBlog
+{
+    Q_OBJECT
+public:
+    explicit MastodonMicroBlog(QObject *parent, const QVariantList &args);
+    virtual ~MastodonMicroBlog();
+
+    virtual void aboutToUnload() override;
+
+    virtual ChoqokEditAccountWidget *createEditAccountWidget(Choqok::Account \
*account, QWidget *parent) override; +
+    virtual void createPost(Choqok::Account *theAccount, Choqok::Post *post) \
override; +
+    virtual Choqok::Account *createNewAccount(const QString &alias) override;
+
+    virtual Choqok::UI::PostWidget *createPostWidget(Choqok::Account *account,
+                                                    Choqok::Post *post,
+                                                    QWidget *parent) override;
+
+    virtual void fetchPost(Choqok::Account *theAccount, Choqok::Post *post) \
override; +
+    virtual QList<Choqok::Post * > loadTimeline(Choqok::Account *account,
+                                                const QString &timelineName) \
override; +
+    virtual void removePost(Choqok::Account *theAccount, Choqok::Post *post) \
override; +
+    virtual QUrl profileUrl(Choqok::Account *account, const Choqok::User &user) \
const override; +
+    virtual void saveTimeline(Choqok::Account *account, const QString &timelineName,
+                              const QList< Choqok::UI::PostWidget * > &timeline) \
override; +
+    virtual Choqok::TimelineInfo *timelineInfo(const QString &timelineName) \
override; +
+    virtual void updateTimelines(Choqok::Account *theAccount) override;
+
+    void toggleReblog(Choqok::Account *theAccount, Choqok::Post *post);
+
+    void toggleFavorite(Choqok::Account *theAccount, Choqok::Post *post);
+
+    static QString userNameFromAcct(const QString &acct);
+    static QString hostFromAcct(const QString &acct);
+
+Q_SIGNALS:
+    void favorite(Choqok::Account *, Choqok::Post *);
+
+protected Q_SLOTS:
+    void slotCreatePost(KJob *job);
+    void slotFavorite(KJob *job);
+    void slotFetchPost(KJob *job);
+    void slotReblog(KJob *job);
+    void slotRemovePost(KJob *job);
+    void slotUpdateTimeline(KJob *job);
+
+protected:
+    static const QString homeTimeline;
+    static const QString publicTimeline;
+
+    QString authorizationMetaData(MastodonAccount *account) const;
+
+    QString lastTimelineId(Choqok::Account *theAccount, const QString &timeline) \
const; +
+    Choqok::Post *readPost(const QVariantMap &var, Choqok::Post *post);
+
+    QList<Choqok::Post * > readTimeline(const QByteArray &buffer);
+
+    void setLastTimelineId(Choqok::Account *theAccount, const QString &timeline,
+                           const QString &id);
+    void setTimelinesInfo();
+
+    QMap<KJob *, Choqok::Account * > m_accountJobs;
+    QMap<KJob *, Choqok::Post * > m_createPostJobs;
+    QMap<KJob *, Choqok::Post * > m_favoriteJobs;
+    QMap<KJob *, Choqok::Post * > m_removePostJobs;
+    QMap<KJob *, Choqok::Post * > m_shareJobs;
+    QMap<QString, Choqok::TimelineInfo * > m_timelinesInfos;
+    QHash<Choqok::Account *, QMap<QString, QString> > m_timelinesLatestIds;
+    QHash<QString, QString> m_timelinesPaths;
+    QMap<KJob *, QString> m_timelinesRequests;
+
+private:
+    class Private;
+    Private *const d;
+
+};
+
+#endif // MASTODONMICROBLOG_H
diff --git a/microblogs/mastodon/mastodonoauth.cpp \
b/microblogs/mastodon/mastodonoauth.cpp new file mode 100644
index 00000000..007b9ae6
--- /dev/null
+++ b/microblogs/mastodon/mastodonoauth.cpp
@@ -0,0 +1,65 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017 Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+
+*/
+
+#include "mastodonoauth.h"
+
+#include <QNetworkReply>
+#include <QUrlQuery>
+
+#include <KIO/AccessManager>
+
+#include "mastodonaccount.h"
+#include "mastodondebug.h"
+#include "mastodonoauthreplyhandler.h"
+
+MastodonOAuth::MastodonOAuth(MastodonAccount *account)
+    : QOAuth2AuthorizationCodeFlow(account),
+      m_replyHandler(0), m_networkAccessManager(0)
+{
+    qCDebug(CHOQOK);
+
+    m_replyHandler = new MastodonOAuthReplyHandler(this);
+    setReplyHandler(m_replyHandler);
+
+    m_networkAccessManager = new KIO::AccessManager(this);
+    setNetworkAccessManager(m_networkAccessManager);
+
+    setClientIdentifier(account->consumerKey());
+    setClientIdentifierSharedKey(account->consumerSecret());
+
+    setScope(QLatin1String("read write follow"));
+
+    setAccessTokenUrl(QUrl(account->host() + QLatin1String("/oauth/token")));
+    setAuthorizationUrl(QUrl(account->host() + QLatin1String("/oauth/authorize")));
+}
+
+MastodonOAuth::~MastodonOAuth()
+{
+    m_replyHandler->deleteLater();
+    m_networkAccessManager->deleteLater();
+}
+
+void MastodonOAuth::getToken(const QString &code)
+{
+    requestAccessToken(code);
+}
diff --git a/microblogs/mastodon/mastodonoauth.h \
b/microblogs/mastodon/mastodonoauth.h new file mode 100644
index 00000000..20bee770
--- /dev/null
+++ b/microblogs/mastodon/mastodonoauth.h
@@ -0,0 +1,55 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017 Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+
+*/
+
+#ifndef MASTODONOAUTH_H
+#define MASTODONOAUTH_H
+
+#include <QOAuth2AuthorizationCodeFlow>
+#include <QNetworkAccessManager>
+
+class MastodonAccount;
+
+namespace KIO {
+class AccessManager;
+}
+
+class MastodonOAuthReplyHandler;
+
+class MastodonOAuth : public QOAuth2AuthorizationCodeFlow
+{
+    Q_OBJECT
+public:
+    explicit MastodonOAuth(MastodonAccount *account);
+    ~MastodonOAuth();
+
+    QByteArray authorizationHeader(const QUrl &requestUrl, \
QNetworkAccessManager::Operation method, +                                   const \
QVariantMap &params = QVariantMap()); +
+    void getToken(const QString &code);
+
+private:
+    MastodonOAuthReplyHandler *m_replyHandler;
+    KIO::AccessManager *m_networkAccessManager;
+};
+
+#endif // MASTODONOAUTH_H
diff --git a/microblogs/mastodon/mastodonoauthreplyhandler.cpp \
b/microblogs/mastodon/mastodonoauthreplyhandler.cpp new file mode 100644
index 00000000..9d14e574
--- /dev/null
+++ b/microblogs/mastodon/mastodonoauthreplyhandler.cpp
@@ -0,0 +1,38 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017 Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+
+*/
+
+#include "mastodonoauthreplyhandler.h"
+
+MastodonOAuthReplyHandler::MastodonOAuthReplyHandler(QObject *parent)
+    : QOAuthOobReplyHandler(parent)
+{
+}
+
+MastodonOAuthReplyHandler::~MastodonOAuthReplyHandler()
+{
+}
+
+QString MastodonOAuthReplyHandler::callback() const
+{
+    return QLatin1String("urn:ietf:wg:oauth:2.0:oob");
+}
diff --git a/microblogs/mastodon/mastodonoauthreplyhandler.h \
b/microblogs/mastodon/mastodonoauthreplyhandler.h new file mode 100644
index 00000000..e7bf642d
--- /dev/null
+++ b/microblogs/mastodon/mastodonoauthreplyhandler.h
@@ -0,0 +1,39 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017 Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+
+*/
+
+#ifndef MASTODONOAUTHREPLYHANDLER_H
+#define MASTODONOAUTHREPLYHANDLER_H
+
+#include <QOAuthOobReplyHandler>
+
+class MastodonOAuthReplyHandler : public QOAuthOobReplyHandler
+{
+    Q_OBJECT
+public:
+    explicit MastodonOAuthReplyHandler(QObject *parent = nullptr);
+    ~MastodonOAuthReplyHandler();
+
+    QString callback() const override;
+};
+
+#endif // MASTODONOAUTHREPLYHANDLER_H
diff --git a/microblogs/mastodon/mastodonpost.cpp \
b/microblogs/mastodon/mastodonpost.cpp new file mode 100644
index 00000000..5fb34aa9
--- /dev/null
+++ b/microblogs/mastodon/mastodonpost.cpp
@@ -0,0 +1,31 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017 Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+*/
+
+#include "mastodonpost.h"
+
+MastodonPost::MastodonPost() : Post()
+{
+}
+
+MastodonPost::~MastodonPost()
+{
+}
diff --git a/microblogs/mastodon/mastodonpost.h b/microblogs/mastodon/mastodonpost.h
new file mode 100644
index 00000000..a5d5fffe
--- /dev/null
+++ b/microblogs/mastodon/mastodonpost.h
@@ -0,0 +1,38 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017 Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+*/
+
+#ifndef MASTODONPOST_H
+#define MASTODONPOST_H
+
+#include <QStringList>
+
+#include "choqoktypes.h"
+
+class MastodonPost : public Choqok::Post
+{
+public:
+    explicit MastodonPost();
+    ~MastodonPost();
+
+};
+
+#endif //MASTODONPOST_H
diff --git a/microblogs/mastodon/mastodonpostwidget.cpp \
b/microblogs/mastodon/mastodonpostwidget.cpp new file mode 100644
index 00000000..ad44e189
--- /dev/null
+++ b/microblogs/mastodon/mastodonpostwidget.cpp
@@ -0,0 +1,138 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017 Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+*/
+
+#include "mastodonpostwidget.h"
+
+#include <QAction>
+#include <QMenu>
+#include <QPushButton>
+
+#include <KLocalizedString>
+
+#include "mediamanager.h"
+#include "textbrowser.h"
+
+#include "mastodonaccount.h"
+#include "mastodondebug.h"
+#include "mastodonmicroblog.h"
+#include "mastodonpost.h"
+
+const QIcon MastodonPostWidget::unFavIcon(Choqok::MediaManager::convertToGrayScale(QIcon::fromTheme(QLatin1String("rating")).pixmap(16)));
 +
+class MastodonPostWidget::Private
+{
+public:
+    QPushButton *btnFavorite;
+};
+
+MastodonPostWidget::MastodonPostWidget(Choqok::Account *account, Choqok::Post *post,
+                                   QWidget *parent):
+    PostWidget(account, post, parent), d(new Private)
+{
+}
+
+MastodonPostWidget::~MastodonPostWidget()
+{
+    delete d;
+}
+
+QString MastodonPostWidget::generateSign()
+{
+    QString ss;
+
+    MastodonPost *post = dynamic_cast<MastodonPost * >(currentPost());
+    MastodonAccount *account = qobject_cast<MastodonAccount * >(currentAccount());
+    MastodonMicroBlog *microblog = qobject_cast<MastodonMicroBlog * \
>(account->microblog()); +    if (post) {
+        if (post->author.userName != account->username()) {
+            ss += QLatin1String("<b><a href=\"") + microblog->profileUrl(account, \
post->author).toDisplayString() +                  + QLatin1String("\" title=\"") + \
post->author.realName + QLatin1String("\">") + +                  \
post->author.userName + QLatin1String("</a></b> - "); +        }
+
+        ss += QLatin1String("<a href=\"") + microblog->postUrl(account, \
post->author.userName, +                                                post->postId) \
+ QLatin1String("\" title=\"") + +              \
post->creationDateTime.toString(Qt::DefaultLocaleLongDate) +              + \
QLatin1String("\">%1</a>"); +
+        if (!post->source.isEmpty()) {
+            ss += QLatin1String(" - ") + post->source;
+        }
+
+        if (!post->repeatedFromUsername.isEmpty()) {
+            ss += QLatin1String(" - ");
+            ss += i18n("Boosted by: ") + \
microblog->userNameFromAcct(post->repeatedFromUsername); +        }
+    } else {
+        qCDebug(CHOQOK) << "post is not a MastodonPost!";
+    }
+
+    return ss;
+}
+
+void MastodonPostWidget::initUi()
+{
+    Choqok::UI::PostWidget::initUi();
+
+    if (isResendAvailable()) {
+        buttons().value(QLatin1String("btnResend"))->setToolTip(i18nc("@info:tooltip", \
"Boost")); +    }
+
+    d->btnFavorite = addButton(QLatin1String("btnFavorite"), i18nc("@info:tooltip", \
"Favourite"), QLatin1String("rating")); +    d->btnFavorite->setCheckable(true);
+    connect(d->btnFavorite, SIGNAL(clicked(bool)), this, SLOT(toggleFavorite()));
+    updateFavStat();
+}
+
+void MastodonPostWidget::slotResendPost()
+{
+    qCDebug(CHOQOK);
+    setReadWithSignal();
+    MastodonMicroBlog *microBlog = qobject_cast<MastodonMicroBlog \
*>(currentAccount()->microblog()); +    microBlog->toggleReblog(currentAccount(), \
currentPost()); +}
+
+void MastodonPostWidget::toggleFavorite()
+{
+    qCDebug(CHOQOK);
+    setReadWithSignal();
+    MastodonMicroBlog *microBlog = qobject_cast<MastodonMicroBlog \
*>(currentAccount()->microblog()); +    connect(microBlog, \
SIGNAL(favorite(Choqok::Account*,Choqok::Post*)), +            this, \
SLOT(slotToggleFavorite(Choqok::Account*,Choqok::Post*))); +    \
microBlog->toggleFavorite(currentAccount(), currentPost()); +}
+
+void MastodonPostWidget::slotToggleFavorite(Choqok::Account *, Choqok::Post *)
+{
+    qCDebug(CHOQOK);
+    updateFavStat();
+}
+
+void MastodonPostWidget::updateFavStat()
+{
+    d->btnFavorite->setChecked(currentPost()->isFavorited);
+    if (currentPost()->isFavorited) {
+        d->btnFavorite->setIcon(QIcon::fromTheme(QLatin1String("rating")));
+    } else {
+        d->btnFavorite->setIcon(unFavIcon);
+    }
+}
diff --git a/microblogs/mastodon/mastodonpostwidget.h \
b/microblogs/mastodon/mastodonpostwidget.h new file mode 100644
index 00000000..3c3753ff
--- /dev/null
+++ b/microblogs/mastodon/mastodonpostwidget.h
@@ -0,0 +1,56 @@
+/*
+    This file is part of Choqok, the KDE micro-blogging client
+
+    Copyright (C) 2017 Andrea Scarpino <scarpino@kde.org>
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of
+    the License or (at your option) version 3 or any later version
+    accepted by the membership of KDE e.V. (or its successor approved
+    by the membership of KDE e.V.), which shall act as a proxy
+    defined in Section 14 of version 3 of the license.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, see http://www.gnu.org/licenses/
+*/
+
+#ifndef MASTODONPOSTWIDGET_H
+#define MASTODONPOSTWIDGET_H
+
+#include "postwidget.h"
+
+class MastodonPostWidget : public Choqok::UI::PostWidget
+{
+    Q_OBJECT
+public:
+    explicit MastodonPostWidget(Choqok::Account *account, Choqok::Post *post, \
QWidget *parent = 0); +    virtual ~MastodonPostWidget();
+
+    virtual QString generateSign() override;
+
+    virtual void initUi() override;
+
+protected Q_SLOTS:
+    virtual void slotResendPost() override;
+
+    void slotToggleFavorite(Choqok::Account *, Choqok::Post *);
+
+    void toggleFavorite();
+
+protected:
+    static const QIcon unFavIcon;
+
+private:
+    void updateFavStat();
+
+    class Private;
+    Private *const d;
+};
+
+#endif // MASTODONPOSTWIDGET_H


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic