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

0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
0002 // SPDX-License-Identifier: GPL-3.0-only
0003 
0004 #include "accountmodel.h"
0005 #include "account/abstractaccount.h"
0006 #include "account/relationship.h"
0007 #include <KLocalizedString>
0008 #include <QCoreApplication>
0009 #include <QUrlQuery>
0010 
0011 AccountModel::AccountModel(QObject *parent)
0012     : TimelineModel(parent)
0013 {
0014     init();
0015 
0016     connect(this, &AccountModel::identityChanged, this, &TimelineModel::nameChanged);
0017     connect(this, &AccountModel::filtersChanged, this, [this] {
0018         fillTimeline();
0019     });
0020 }
0021 
0022 bool AccountModel::isSelf() const
0023 {
0024     if (m_identity == nullptr) {
0025         return false;
0026     }
0027 
0028     return m_account->identity()->id() == m_identity->id();
0029 }
0030 
0031 QString AccountModel::displayName() const
0032 {
0033     if (!m_identity) {
0034         return i18n("Loading");
0035     }
0036 
0037     return m_identity->displayNameHtml();
0038 }
0039 
0040 void AccountModel::fillTimeline(const QString &fromId)
0041 {
0042     if (m_accountId.isEmpty() || m_accountId.isNull()) {
0043         return;
0044     }
0045     setLoading(true);
0046 
0047     // Fetch pinned posts if we are starting from the top
0048     const auto fetchPinned = fromId.isNull() && !m_excludePinned;
0049     auto uriStatus = m_account->apiUrl(QStringLiteral("/api/v1/accounts/%1/statuses").arg(m_accountId));
0050 
0051     auto statusQuery = QUrlQuery();
0052     if (m_excludeReplies) {
0053         statusQuery.addQueryItem("exclude_replies", "true");
0054     }
0055     if (m_excludeBoosts) {
0056         statusQuery.addQueryItem("exclude_reblogs", "true");
0057     }
0058     if (m_onlyMedia) {
0059         statusQuery.addQueryItem("only_media", "true");
0060     }
0061     if (!m_tagged.isEmpty()) {
0062         statusQuery.addQueryItem("tagged", m_tagged);
0063     }
0064     if (!fromId.isNull()) {
0065         statusQuery.addQueryItem("max_id", fromId);
0066     }
0067     if (!statusQuery.isEmpty()) {
0068         uriStatus.setQuery(statusQuery);
0069     }
0070 
0071     auto uriPinned = m_account->apiUrl(QStringLiteral("/api/v1/accounts/%1/statuses").arg(m_accountId));
0072     uriPinned.setQuery(QUrlQuery{{
0073         "pinned",
0074         "true",
0075     }});
0076 
0077     const auto account = m_account;
0078     const auto id = m_accountId;
0079 
0080     auto handleError = [this](QNetworkReply *reply) {
0081         Q_UNUSED(reply);
0082         setLoading(false);
0083     };
0084 
0085     auto onFetchPinned = [this, id, account](QNetworkReply *reply) {
0086         if (m_account != account || m_accountId != id) {
0087             setLoading(false);
0088             return;
0089         }
0090         const auto data = reply->readAll();
0091         const auto doc = QJsonDocument::fromJson(data);
0092         if (!doc.isArray()) {
0093             setLoading(false);
0094             return;
0095         }
0096         const auto array = doc.array();
0097         if (array.isEmpty()) {
0098             setLoading(false);
0099             return;
0100         }
0101 
0102         QList<Post *> posts;
0103         std::transform(array.cbegin(), array.cend(), std::back_inserter(posts), [this](const QJsonValue &value) {
0104             auto post = new Post(m_account, value.toObject(), this);
0105             post->setPinned(true);
0106             return post;
0107         });
0108         beginInsertRows({}, 0, posts.size() - 1);
0109         m_timeline = posts + m_timeline;
0110         endInsertRows();
0111         setLoading(false);
0112     };
0113 
0114     auto onFetchAccount = [account, id, fetchPinned, uriPinned, handleError, onFetchPinned, fromId, this](QNetworkReply *reply) {
0115         if (m_account != account || m_accountId != id) {
0116             setLoading(false);
0117             return;
0118         }
0119 
0120         // if we just restarted the fetch (fromId is null) then we must clear the previous array
0121         // this can happen if we just entered the profile page (okay, just a no-op) or if the filters change
0122         if (fromId.isNull()) {
0123             beginResetModel();
0124             qDeleteAll(m_timeline);
0125             m_timeline.clear();
0126             endResetModel();
0127         }
0128 
0129         fetchedTimeline(reply->readAll());
0130         if (fetchPinned) {
0131             m_account->get(uriPinned, true, this, onFetchPinned, handleError);
0132         } else {
0133             setLoading(false);
0134         }
0135     };
0136 
0137     m_account->get(uriStatus, true, this, onFetchAccount, handleError);
0138 }
0139 
0140 Identity *AccountModel::identity() const
0141 {
0142     return m_identity.get();
0143 }
0144 
0145 QString AccountModel::accountId() const
0146 {
0147     return m_accountId;
0148 }
0149 
0150 void AccountModel::setAccountId(const QString &accountId)
0151 {
0152     if (accountId == m_accountId || accountId.isEmpty()) {
0153         return;
0154     }
0155     m_accountId = accountId;
0156     Q_EMIT accountIdChanged();
0157 
0158     if (!m_account->identityCached(accountId)) {
0159         QUrl uriAccount(m_account->instanceUri());
0160         uriAccount.setPath(QStringLiteral("/api/v1/accounts/%1").arg(accountId));
0161 
0162         m_account->get(uriAccount, true, this, [this, accountId](QNetworkReply *reply) {
0163             const auto data = reply->readAll();
0164             const auto doc = QJsonDocument::fromJson(data);
0165 
0166             m_identity = m_account->identityLookup(accountId, doc.object());
0167             Q_EMIT identityChanged();
0168             updateRelationships();
0169         });
0170     } else {
0171         m_identity = m_account->identityLookup(accountId, {});
0172         Q_EMIT identityChanged();
0173         updateRelationships();
0174     }
0175     Q_EMIT accountIdChanged();
0176 
0177     fillTimeline();
0178 }
0179 
0180 AbstractAccount *AccountModel::account() const
0181 {
0182     return m_account;
0183 }
0184 
0185 void AccountModel::updateRelationships()
0186 {
0187     if (m_account->identity()->id() == m_identity->id()) {
0188         return;
0189     }
0190 
0191     // Fetch relationship. Don't cache this; it's lightweight.
0192     QUrl uriRelationship(m_account->instanceUri());
0193     uriRelationship.setPath(QStringLiteral("/api/v1/accounts/relationships"));
0194     uriRelationship.setQuery(QUrlQuery{
0195         {QStringLiteral("id[]"), m_identity->id()},
0196     });
0197 
0198     m_account->get(uriRelationship, true, this, [this](QNetworkReply *reply) {
0199         const auto doc = QJsonDocument::fromJson(reply->readAll());
0200         if (!doc.isArray()) {
0201             qWarning() << "Data returned from Relationship network request is not an array"
0202                        << "data: " << doc;
0203             return;
0204         }
0205 
0206         // We only are requesting for a single relationship, so doc should only contain one element
0207         m_identity->setRelationship(new Relationship(m_identity.get(), doc[0].toObject()));
0208         Q_EMIT identityChanged();
0209     });
0210 }