File indexing completed on 2024-05-12 16:28:05

0001 // SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
0002 // SPDX-License-Identifier: GPL-3.0-or-later
0003 
0004 #include "socialgraphmodel.h"
0005 
0006 #include "identity.h"
0007 
0008 #include "abstractaccount.h"
0009 #include "accountmanager.h"
0010 #include "relationship.h"
0011 #include <KLocalizedString>
0012 #include <QFile>
0013 #include <QJsonDocument>
0014 #include <QNetworkReply>
0015 #include <qstringliteral.h>
0016 
0017 SocialGraphModel::SocialGraphModel(QObject *parent)
0018     : QAbstractListModel(parent)
0019 {
0020 }
0021 
0022 QString SocialGraphModel::name() const
0023 {
0024     return m_followListName;
0025 }
0026 
0027 QString SocialGraphModel::displayName() const
0028 {
0029     if (m_followListName == "request") {
0030         return i18nc("@title", "Follow Requests");
0031     } else if (m_followListName == "followers") {
0032         return i18nc("@title", "Followers");
0033     } else if (m_followListName == "following") {
0034         return i18nc("@title", "Following");
0035     } else if (m_followListName == "mutes") {
0036         return i18nc("@title", "Muted Accounts");
0037     } else if (m_followListName == "blocks") {
0038         return i18nc("@title", "Blocked Accounts");
0039     } else if (m_followListName == "featured") {
0040         return i18nc("@title", "Featured Accounts");
0041     }
0042     return {};
0043 }
0044 
0045 QString SocialGraphModel::placeholderText() const
0046 {
0047     if (m_followListName == "request") {
0048         return i18n("No follow requests");
0049     } else if (m_followListName == "followers") {
0050         return i18n("No followers");
0051     } else if (m_followListName == "following") {
0052         return i18n("No followed accounts");
0053     } else if (m_followListName == "mutes") {
0054         return i18n("No muted accounts");
0055     } else if (m_followListName == "blocks") {
0056         return i18n("No blocked accounts");
0057     } else if (m_followListName == "featured") {
0058         return i18n("No featured accounts");
0059     }
0060     return {};
0061 }
0062 
0063 void SocialGraphModel::setName(const QString &followlistname)
0064 {
0065     if (followlistname == m_followListName) {
0066         return;
0067     }
0068 
0069     m_followListName = followlistname;
0070     Q_EMIT nameChanged();
0071     fillTimeline();
0072 }
0073 
0074 QString SocialGraphModel::accountId() const
0075 {
0076     return m_accountId;
0077 }
0078 
0079 void SocialGraphModel::setAccountId(const QString &accountId)
0080 {
0081     m_accountId = accountId;
0082     Q_EMIT accountIdChanged();
0083     fillTimeline();
0084 }
0085 
0086 QVariant SocialGraphModel::data(const QModelIndex &index, int role) const
0087 {
0088     Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid));
0089 
0090     const auto identity = m_accounts[index.row()].get();
0091     switch (role) {
0092     case CustomRoles::IdentityRole:
0093         return QVariant::fromValue<Identity *>(identity);
0094     default:
0095         Q_UNREACHABLE();
0096     }
0097 }
0098 
0099 int SocialGraphModel::rowCount(const QModelIndex &) const
0100 {
0101     return m_accounts.count();
0102 }
0103 
0104 QHash<int, QByteArray> SocialGraphModel::roleNames() const
0105 {
0106     return {
0107         {CustomRoles::IdentityRole, "identity"},
0108     };
0109 }
0110 
0111 bool SocialGraphModel::loading() const
0112 {
0113     return m_loading;
0114 }
0115 
0116 void SocialGraphModel::setLoading(bool loading)
0117 {
0118     if (m_loading == loading) {
0119         return;
0120     }
0121     m_loading = loading;
0122     Q_EMIT loadingChanged();
0123 }
0124 
0125 bool SocialGraphModel::isFollowRequest() const
0126 {
0127     return m_followListName == "request";
0128 }
0129 
0130 bool SocialGraphModel::isFollowing() const
0131 {
0132     return m_followListName == "following";
0133 }
0134 
0135 bool SocialGraphModel::isFollower() const
0136 {
0137     return m_followListName == "followers";
0138 }
0139 
0140 void SocialGraphModel::actionAllow(const QModelIndex &index)
0141 {
0142     auto account = AccountManager::instance().selectedAccount();
0143 
0144     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid))
0145         return;
0146 
0147     auto requestIdentity = m_accounts[index.row()].get();
0148     const auto requestIdentityId = requestIdentity->id();
0149 
0150     account->post(account->apiUrl(QString("/api/v1/follow_requests/%1/authorize").arg(requestIdentityId)),
0151                   QJsonDocument{},
0152                   true,
0153                   this,
0154                   [this, requestIdentity, index](QNetworkReply *reply) {
0155                       const auto newRelation = QJsonDocument::fromJson(reply->readAll()).object();
0156 
0157                       m_accounts[index.row()]->setRelationship(new Relationship(requestIdentity, newRelation));
0158 
0159                       beginRemoveRows(QModelIndex(), index.row(), index.row());
0160                       m_accounts.removeAt(index.row());
0161                       endRemoveRows();
0162                   });
0163 }
0164 
0165 void SocialGraphModel::actionDeny(const QModelIndex &index)
0166 {
0167     auto account = AccountManager::instance().selectedAccount();
0168 
0169     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid))
0170         return;
0171 
0172     auto requestIdentity = m_accounts[index.row()].get();
0173     const auto requestIdentityId = requestIdentity->id();
0174 
0175     account->post(account->apiUrl(QString("/api/v1/follow_requests/%1/reject").arg(requestIdentityId)),
0176                   QJsonDocument{},
0177                   true,
0178                   this,
0179                   [this, requestIdentity, index](QNetworkReply *reply) {
0180                       const auto newRelation = QJsonDocument::fromJson(reply->readAll()).object();
0181 
0182                       m_accounts[index.row()]->setRelationship(new Relationship(requestIdentity, newRelation));
0183 
0184                       beginRemoveRows(QModelIndex(), index.row(), index.row());
0185                       m_accounts.removeAt(index.row());
0186                       endRemoveRows();
0187                   });
0188 }
0189 
0190 bool SocialGraphModel::canFetchMore(const QModelIndex &parent) const
0191 {
0192     Q_UNUSED(parent);
0193     return !m_next.isEmpty();
0194 }
0195 
0196 void SocialGraphModel::fetchMore(const QModelIndex &parent)
0197 {
0198     Q_UNUSED(parent);
0199 
0200     fillTimeline();
0201 }
0202 
0203 void SocialGraphModel::fillTimeline()
0204 {
0205     auto account = AccountManager::instance().selectedAccount();
0206 
0207     if (m_followListName.isEmpty() || m_followListName.isNull()) {
0208         return;
0209     }
0210 
0211     if ((m_followListName == "followers" || m_followListName == "following") && (m_accountId.isEmpty() || m_accountId.isNull())) {
0212         return;
0213     }
0214 
0215     if (m_loading) {
0216         return;
0217     }
0218     setLoading(true);
0219 
0220     QString uri;
0221     if (m_followListName == "request") {
0222         uri = "/api/v1/follow_requests";
0223     } else if (m_followListName == "followers") {
0224         uri = QStringLiteral("/api/v1/accounts/%1/followers").arg(m_accountId);
0225     } else if (m_followListName == "following") {
0226         uri = QStringLiteral("/api/v1/accounts/%1/following").arg(m_accountId);
0227     } else if (m_followListName == "mutes") {
0228         uri = QStringLiteral("/api/v1/mutes");
0229     } else if (m_followListName == "blocks") {
0230         uri = QStringLiteral("/api/v1/blocks");
0231     } else if (m_followListName == "featured") {
0232         uri = QStringLiteral("/api/v1/endorsements");
0233     }
0234 
0235     QUrl url;
0236     if (m_next.isEmpty()) {
0237         url = account->apiUrl(uri);
0238     } else {
0239         url = m_next;
0240     }
0241 
0242     account->get(url, true, this, [this, account](QNetworkReply *reply) {
0243         const auto followRequestResult = QJsonDocument::fromJson(reply->readAll());
0244         const auto accounts = followRequestResult.array();
0245 
0246         if (!accounts.isEmpty()) {
0247             static QRegularExpression re("<(.*)>; rel=\"next\"");
0248             const auto next = reply->rawHeader(QByteArrayLiteral("Link"));
0249             const auto match = re.match(next);
0250             if (re.isValid()) {
0251                 m_next = QUrl::fromUserInput(match.captured(1));
0252             }
0253 
0254             QList<std::shared_ptr<Identity>> fetchedAccounts;
0255 
0256             std::transform(
0257                 accounts.cbegin(),
0258                 accounts.cend(),
0259                 std::back_inserter(fetchedAccounts),
0260                 [account](const QJsonValue &value) -> auto{
0261                     const auto identityJson = value.toObject();
0262                     return account->identityLookup(identityJson["id"].toString(), identityJson);
0263                 });
0264 
0265             beginInsertRows({}, m_accounts.size(), m_accounts.size() + fetchedAccounts.size() - 1);
0266             m_accounts += fetchedAccounts;
0267             endInsertRows();
0268         }
0269 
0270         setLoading(false);
0271     });
0272 }