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

0001 // SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
0002 // SPDX-License-Identifier: LGPL-2.1-or-later
0003 
0004 #include "profileeditor.h"
0005 
0006 #include "abstractaccount.h"
0007 
0008 #include <KLocalizedString>
0009 
0010 using namespace Qt::Literals::StringLiterals;
0011 
0012 ProfileEditorBackend::ProfileEditorBackend(QObject *parent)
0013     : QObject(parent)
0014 {
0015 }
0016 
0017 ProfileEditorBackend::~ProfileEditorBackend() = default;
0018 
0019 AbstractAccount *ProfileEditorBackend::account() const
0020 {
0021     return m_account;
0022 }
0023 
0024 void ProfileEditorBackend::setAccount(AbstractAccount *account)
0025 {
0026     if (m_account == account) {
0027         return;
0028     }
0029     m_account = account;
0030     Q_EMIT accountChanged();
0031     if (m_account) {
0032         fetchAccountInfo();
0033     }
0034 }
0035 
0036 QString ProfileEditorBackend::displayName() const
0037 {
0038     return m_displayName;
0039 }
0040 
0041 QString ProfileEditorBackend::displayNameHtml() const
0042 {
0043     return CustomEmoji::replaceCustomEmojis(m_account->customEmojis(), m_displayName);
0044 }
0045 
0046 void ProfileEditorBackend::setDisplayName(const QString &displayName)
0047 {
0048     if (m_displayName == displayName) {
0049         return;
0050     }
0051     m_displayName = displayName;
0052     Q_EMIT displayNameChanged();
0053 }
0054 
0055 QString ProfileEditorBackend::note() const
0056 {
0057     return m_note;
0058 }
0059 
0060 void ProfileEditorBackend::setNote(const QString &note)
0061 {
0062     if (m_note == note) {
0063         return;
0064     }
0065     m_note = note;
0066     Q_EMIT noteChanged();
0067 }
0068 
0069 bool ProfileEditorBackend::bot() const
0070 {
0071     return m_bot;
0072 }
0073 
0074 void ProfileEditorBackend::setBot(bool bot)
0075 {
0076     if (m_bot == bot) {
0077         return;
0078     }
0079     m_bot = bot;
0080     Q_EMIT botChanged();
0081 }
0082 
0083 bool ProfileEditorBackend::discoverable() const
0084 {
0085     return m_discoverable;
0086 }
0087 
0088 void ProfileEditorBackend::setDiscoverable(bool discoverable)
0089 {
0090     if (m_discoverable == discoverable) {
0091         return;
0092     }
0093     m_discoverable = discoverable;
0094     Q_EMIT discoverableChanged();
0095 }
0096 
0097 bool ProfileEditorBackend::locked() const
0098 {
0099     return m_locked;
0100 }
0101 
0102 void ProfileEditorBackend::setLocked(bool locked)
0103 {
0104     if (m_locked == locked) {
0105         return;
0106     }
0107     m_locked = locked;
0108     Q_EMIT lockedChanged();
0109 }
0110 
0111 QUrl ProfileEditorBackend::avatarUrl() const
0112 {
0113     return m_avatarUrl;
0114 }
0115 
0116 void ProfileEditorBackend::setAvatarUrl(const QUrl &avatarUrl)
0117 {
0118     if (avatarUrl == m_avatarUrl) {
0119         return;
0120     }
0121     m_avatarUrl = avatarUrl;
0122     Q_EMIT avatarUrlChanged();
0123 }
0124 
0125 QUrl ProfileEditorBackend::backgroundUrl() const
0126 {
0127     return m_backgroundUrl;
0128 }
0129 
0130 namespace
0131 {
0132 
0133 auto operator""_MiB(unsigned long long const x) -> long
0134 {
0135     return 1024L * 1024L * x;
0136 }
0137 
0138 QString checkImage(const QUrl &url)
0139 {
0140     if (!url.isLocalFile() || url.isEmpty()) {
0141         return {};
0142     }
0143 
0144     QFileInfo fileInfo(url.toLocalFile());
0145     if (fileInfo.size() > 2_MiB) {
0146         return i18n("Image is too big");
0147     }
0148 
0149     QMimeDatabase mimeDatabase;
0150     const auto mimeType = mimeDatabase.mimeTypeForFile(fileInfo);
0151 
0152     static const QSet<QString> allowedMimeType = {
0153         QStringLiteral("image/png"),
0154         QStringLiteral("image/jpeg"),
0155         QStringLiteral("image/gif"),
0156     };
0157 
0158     if (!allowedMimeType.contains(mimeType.name())) {
0159         return i18n("Unsupported image file. Only jpeg, png and gif are supported.");
0160     }
0161 
0162     return {};
0163 }
0164 }
0165 
0166 QString ProfileEditorBackend::backgroundUrlError() const
0167 {
0168     return checkImage(m_backgroundUrl);
0169 }
0170 
0171 QString ProfileEditorBackend::avatarUrlError() const
0172 {
0173     return checkImage(m_avatarUrl);
0174 }
0175 
0176 void ProfileEditorBackend::setBackgroundUrl(const QUrl &backgroundUrl)
0177 {
0178     if (backgroundUrl == m_backgroundUrl) {
0179         return;
0180     }
0181     m_backgroundUrl = backgroundUrl;
0182     Q_EMIT backgroundUrlChanged();
0183 }
0184 
0185 void ProfileEditorBackend::fetchAccountInfo()
0186 {
0187     Q_ASSERT_X(m_account, Q_FUNC_INFO, "Fetch account called without account");
0188 
0189     m_account->get(m_account->apiUrl(QStringLiteral("/api/v1/accounts/verify_credentials")), true, this, [this](QNetworkReply *reply) {
0190         const auto json = QJsonDocument::fromJson(reply->readAll());
0191         Q_ASSERT(json.isObject());
0192         const auto obj = json.object();
0193         setDisplayName(obj["display_name"_L1].toString());
0194         const auto source = obj["source"_L1].toObject();
0195         setNote(source["note"_L1].toString());
0196         setBot(obj["bot"_L1].toBool());
0197         setBackgroundUrl(QUrl(obj["header_static"_L1].toString()));
0198         setAvatarUrl(QUrl(obj["avatar_static"_L1].toString()));
0199         setLocked(obj["locked"_L1].toBool());
0200         setDiscoverable(obj["discoverable"_L1].toBool());
0201     });
0202 }
0203 
0204 void ProfileEditorBackend::save()
0205 {
0206     const QUrl url = m_account->apiUrl(QStringLiteral("/api/v1/accounts/update_credentials"));
0207 
0208     auto multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
0209 
0210     QHttpPart displayNamePart;
0211     displayNamePart.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"display_name\""));
0212     displayNamePart.setBody(displayName().toUtf8());
0213     multiPart->append(displayNamePart);
0214 
0215     QHttpPart notePart;
0216     notePart.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"note\""));
0217     notePart.setBody(note().toUtf8());
0218     multiPart->append(notePart);
0219 
0220     QHttpPart lockedPart;
0221     lockedPart.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"locked\""));
0222     lockedPart.setBody(locked() ? "1" : "0");
0223     multiPart->append(lockedPart);
0224 
0225     QHttpPart botPart;
0226     botPart.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"bot\""));
0227     botPart.setBody(bot() ? "1" : "0");
0228     multiPart->append(botPart);
0229 
0230     QHttpPart discoverablePart;
0231     discoverablePart.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"discoverable\""));
0232     discoverablePart.setBody(discoverable() ? "1" : "0");
0233     multiPart->append(discoverablePart);
0234 
0235     QMimeDatabase mimeDatabase;
0236 
0237     if (backgroundUrl().isLocalFile()) {
0238         auto file = new QFile(backgroundUrl().toLocalFile());
0239         const auto mime = mimeDatabase.mimeTypeForUrl(backgroundUrl());
0240         if (file->open(QIODevice::ReadOnly)) {
0241             QHttpPart headerPart;
0242             headerPart.setHeader(QNetworkRequest::ContentTypeHeader, mime.name());
0243             headerPart.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"header\"; filename=\"header\""));
0244             headerPart.setBodyDevice(file);
0245             file->setParent(multiPart);
0246             multiPart->append(headerPart);
0247         }
0248     } else if (backgroundUrl().isEmpty()) {
0249         QHttpPart headerPart;
0250         headerPart.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"header\""));
0251         multiPart->append(headerPart);
0252     }
0253 
0254     if (avatarUrl().isLocalFile()) {
0255         auto file = new QFile(avatarUrl().toLocalFile());
0256         const auto mime = mimeDatabase.mimeTypeForUrl(avatarUrl());
0257         if (file->open(QIODevice::ReadOnly)) {
0258             QHttpPart headerPart;
0259             headerPart.setHeader(QNetworkRequest::ContentTypeHeader, mime.name());
0260             headerPart.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"avatar\"; filename=\"avatar\""));
0261             headerPart.setBodyDevice(file);
0262             file->setParent(multiPart);
0263             multiPart->append(headerPart);
0264         }
0265     } else if (avatarUrl().isEmpty()) {
0266         QHttpPart headerPart;
0267         headerPart.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"avatar\""));
0268         multiPart->append(headerPart);
0269     }
0270 
0271     m_account->patch(url, multiPart, true, this, [=](QNetworkReply *reply) {
0272         multiPart->setParent(reply);
0273         Q_EMIT sendNotification(i18n("Account details saved"));
0274         fetchAccountInfo();
0275     });
0276 }
0277 
0278 #include "moc_profileeditor.cpp"