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 ¬e) 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"