File indexing completed on 2024-05-12 05:04:10

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