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 }