File indexing completed on 2024-12-08 12:55:20

0001 // SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
0002 // SPDX-License-Identifier: LGPL-2.1-or-later
0003 
0004 #include "posteditorbackend.h"
0005 #include "account/abstractaccount.h"
0006 #include "account/accountmanager.h"
0007 #include "attachmenteditormodel.h"
0008 #include "config.h"
0009 #include "utils/utils.h"
0010 #include <KLocalizedString>
0011 #include <QJsonArray>
0012 #include <QJsonDocument>
0013 #include <QUuid>
0014 
0015 PostEditorBackend::PostEditorBackend(QObject *parent)
0016     : QObject(parent)
0017     , m_idenpotencyKey(QUuid::createUuid().toString())
0018     , m_language(AccountManager::instance().selectedAccount()->preferences()->defaultLanguage())
0019     , m_poll(new PollEditorBackend(this))
0020     , m_account(AccountManager::instance().selectedAccount())
0021     , m_attachmentEditorModel(new AttachmentEditorModel(this, m_account))
0022 {
0023 }
0024 
0025 PostEditorBackend::~PostEditorBackend() = default;
0026 
0027 QString PostEditorBackend::id() const
0028 {
0029     return m_id;
0030 }
0031 
0032 void PostEditorBackend::setId(const QString &id)
0033 {
0034     m_id = id;
0035 }
0036 
0037 QString PostEditorBackend::status() const
0038 {
0039     return m_status;
0040 }
0041 
0042 void PostEditorBackend::setStatus(const QString &status)
0043 {
0044     if (m_status == status) {
0045         return;
0046     }
0047     m_status = status;
0048     Q_EMIT statusChanged();
0049 }
0050 
0051 QString PostEditorBackend::spoilerText() const
0052 {
0053     return m_spoilerText;
0054 }
0055 
0056 void PostEditorBackend::setSpoilerText(const QString &spoilerText)
0057 {
0058     if (m_spoilerText == spoilerText) {
0059         return;
0060     }
0061     m_spoilerText = spoilerText;
0062     Q_EMIT spoilerTextChanged();
0063 }
0064 
0065 QString PostEditorBackend::inReplyTo() const
0066 {
0067     return m_inReplyTo;
0068 }
0069 
0070 void PostEditorBackend::setInReplyTo(const QString &inReplyTo)
0071 {
0072     if (m_inReplyTo == inReplyTo) {
0073         return;
0074     }
0075     m_inReplyTo = inReplyTo;
0076     Q_EMIT inReplyToChanged();
0077 }
0078 
0079 Post::Visibility PostEditorBackend::visibility() const
0080 {
0081     return m_visibility;
0082 }
0083 
0084 void PostEditorBackend::setVisibility(Post::Visibility visibility)
0085 {
0086     if (m_visibility == visibility) {
0087         return;
0088     }
0089     m_visibility = visibility;
0090     Q_EMIT visibilityChanged();
0091 }
0092 
0093 QString PostEditorBackend::language() const
0094 {
0095     return m_language;
0096 }
0097 
0098 void PostEditorBackend::setLanguage(const QString &language)
0099 {
0100     if (m_language == language) {
0101         return;
0102     }
0103     m_language = language;
0104     Q_EMIT languageChanged();
0105 }
0106 
0107 QDateTime PostEditorBackend::scheduledAt() const
0108 {
0109     return m_scheduledAt;
0110 }
0111 
0112 void PostEditorBackend::setScheduledAt(const QDateTime &scheduledAt)
0113 {
0114     if (m_scheduledAt == scheduledAt) {
0115         return;
0116     }
0117     m_scheduledAt = scheduledAt;
0118     Q_EMIT scheduledAtChanged();
0119 }
0120 
0121 AttachmentEditorModel *PostEditorBackend::attachmentEditorModel() const
0122 {
0123     return m_attachmentEditorModel;
0124 }
0125 
0126 QStringList PostEditorBackend::mentions() const
0127 {
0128     return m_mentions;
0129 }
0130 
0131 void PostEditorBackend::setMentions(const QStringList &mentions)
0132 {
0133     if (m_mentions == mentions) {
0134         return;
0135     }
0136     m_mentions = mentions;
0137     Q_EMIT mentionsChanged();
0138 }
0139 
0140 bool PostEditorBackend::sensitive() const
0141 {
0142     return m_sensitive;
0143 }
0144 
0145 void PostEditorBackend::setSensitive(bool sensitive)
0146 {
0147     if (m_sensitive == sensitive) {
0148         return;
0149     }
0150     m_sensitive = sensitive;
0151     Q_EMIT sensitiveChanged();
0152 }
0153 
0154 AbstractAccount *PostEditorBackend::account() const
0155 {
0156     return m_account;
0157 }
0158 
0159 void PostEditorBackend::setAccount(AbstractAccount *account)
0160 {
0161     if (m_account == account) {
0162         return;
0163     }
0164     m_account = account;
0165     Q_EMIT accountChanged();
0166 }
0167 
0168 int PostEditorBackend::charactersLeft() const
0169 {
0170     if (m_account == nullptr) {
0171         return 0;
0172     }
0173 
0174     QRegularExpression re{R"((http|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-]))"};
0175     re.setPatternOptions(QRegularExpression::DontCaptureOption);
0176 
0177     const auto matches = re.match(m_status).capturedTexts();
0178 
0179     // We want to accumulate each link, and then "add back" the characters you should
0180     // have, taking the difference from charactersPreservedPerUrl.
0181     const int sum = std::accumulate(matches.constBegin(), matches.constEnd(), 0, [this](int sum, const QString &link) {
0182         return sum + link.length() - m_account->charactersReservedPerUrl();
0183     });
0184 
0185     return m_account->maxPostLength() - m_status.length() + sum;
0186 }
0187 
0188 QJsonDocument PostEditorBackend::toJsonDocument() const
0189 {
0190     QJsonObject obj;
0191 
0192     obj["spoiler_text"] = m_spoilerText;
0193     obj["status"] = m_status;
0194     obj["sensitive"] = m_sensitive;
0195     obj["visibility"] = visibilityToString(m_visibility);
0196 
0197     if (!m_inReplyTo.isEmpty()) {
0198         obj["in_reply_to_id"] = m_inReplyTo;
0199     }
0200 
0201     auto media_ids = QJsonArray();
0202     for (const auto &att : qAsConst(m_attachmentEditorModel->attachments())) {
0203         media_ids.append(att->m_id);
0204     }
0205 
0206     obj["media_ids"] = media_ids;
0207     obj["language"] = m_language;
0208 
0209     if (m_pollEnabled) {
0210         obj["poll"] = m_poll->toJsonObject();
0211     }
0212 
0213     return QJsonDocument(obj);
0214 }
0215 
0216 void PostEditorBackend::save()
0217 {
0218     QUrl post_status_url = m_account->apiUrl("/api/v1/statuses");
0219     auto doc = toJsonDocument();
0220 
0221     QHash<QByteArray, QByteArray> headers;
0222     headers["Idempotency-Key"] = m_idenpotencyKey.toUtf8();
0223 
0224     m_account->post(
0225         post_status_url,
0226         doc,
0227         true,
0228         this,
0229         [=](QNetworkReply *) {
0230             Q_EMIT posted("");
0231         },
0232         [=](QNetworkReply *reply) {
0233             auto data = reply->readAll();
0234             auto doc = QJsonDocument::fromJson(data);
0235             auto obj = doc.object();
0236 
0237             if (obj.contains("error")) {
0238                 Q_EMIT posted(obj["error"].toString());
0239             } else {
0240                 Q_EMIT posted(i18n("An unknown error occurred."));
0241             }
0242         },
0243         headers);
0244 }
0245 
0246 void PostEditorBackend::edit()
0247 {
0248     QUrl edit_status_url = m_account->apiUrl(QString("/api/v1/statuses/%1").arg(m_id));
0249     auto doc = toJsonDocument();
0250 
0251     m_account->put(edit_status_url, doc, true, this, [=](QNetworkReply *reply) {
0252         auto data = reply->readAll();
0253         auto doc = QJsonDocument::fromJson(data);
0254         auto obj = doc.object();
0255 
0256         Q_EMIT editComplete(obj);
0257     });
0258 }