File indexing completed on 2024-12-08 12:53:48
0001 /* 0002 SPDX-FileCopyrightText: 2017-2024 Laurent Montel <montel@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "utils.h" 0008 #include "colorsandmessageviewstyle.h" 0009 #include "ruqola_debug.h" 0010 #include <KLocalizedString> 0011 0012 #include <KColorScheme> 0013 #include <QCryptographicHash> 0014 #include <QDateTime> 0015 #include <QJsonDocument> 0016 #include <QRegularExpression> 0017 0018 #include <TextEmoticonsCore/EmoticonUnicodeUtils> 0019 0020 QUrl Utils::generateServerUrl(const QString &url) 0021 { 0022 if (url.isEmpty()) { 0023 return {}; 0024 } 0025 QString serverUrl = url; 0026 if (serverUrl.startsWith(QLatin1String("https://"))) { 0027 serverUrl.replace(QLatin1String("https://"), QLatin1String("wss://")); 0028 } else if (serverUrl.startsWith(QLatin1String("http://"))) { 0029 serverUrl.replace(QLatin1String("http://"), QLatin1String("ws://")); 0030 } else { 0031 serverUrl = QLatin1String("wss://") + serverUrl; 0032 } 0033 return QUrl(serverUrl + QStringLiteral("/websocket")); 0034 } 0035 0036 QString Utils::extractRoomUserFromUrl(QString url) 0037 { 0038 url.remove(QStringLiteral("ruqola:/user/")); 0039 url.remove(QStringLiteral("ruqola:/room/")); 0040 return url; 0041 } 0042 0043 QString Utils::formatQuotedRichText(const QuotedRichTextInfo &info) 0044 { 0045 // Qt's support for borders is limited to tables, so we have to jump through some hoops... 0046 const auto backgroundColor = ColorsAndMessageViewStyle::self().schemeView().background(KColorScheme::AlternateBackground).color().name(); 0047 const auto borderColor = ColorsAndMessageViewStyle::self().schemeView().foreground(KColorScheme::LinkText).color().name(); 0048 QString dateTimeInfo; 0049 if (!info.displayTime.isEmpty()) { 0050 if (!info.url.isEmpty()) { 0051 dateTimeInfo = QLatin1Char('\n') + QStringLiteral("<a href='%1'>%2</a>").arg(info.url, info.displayTime); 0052 } else { 0053 dateTimeInfo = QLatin1Char('\n') + info.displayTime; 0054 } 0055 } 0056 return QStringLiteral("<table><tr><td style='background-color:%1; padding-left: 5px; border-left: 5px solid %2'>").arg(backgroundColor, borderColor) 0057 + info.richText + dateTimeInfo + QStringLiteral("</td></tr></table>"); 0058 } 0059 0060 QString Utils::presenceStatusToString(User::PresenceStatus status) 0061 { 0062 switch (status) { 0063 case User::PresenceStatus::PresenceOnline: 0064 return QStringLiteral("online"); 0065 case User::PresenceStatus::PresenceBusy: 0066 return QStringLiteral("busy"); 0067 case User::PresenceStatus::PresenceAway: 0068 return QStringLiteral("away"); 0069 case User::PresenceStatus::PresenceOffline: 0070 return QStringLiteral("offline"); 0071 case User::PresenceStatus::Unknown: 0072 return {}; 0073 } 0074 0075 return {}; 0076 } 0077 0078 QString Utils::iconFromPresenceStatus(User::PresenceStatus status) 0079 { 0080 switch (status) { 0081 case User::PresenceStatus::PresenceOnline: 0082 return QStringLiteral("user-online"); 0083 case User::PresenceStatus::PresenceBusy: 0084 return QStringLiteral("user-busy"); 0085 case User::PresenceStatus::PresenceAway: 0086 return QStringLiteral("user-away"); 0087 case User::PresenceStatus::PresenceOffline: 0088 return QStringLiteral("user-offline"); 0089 case User::PresenceStatus::Unknown: 0090 return QStringLiteral("unknown"); 0091 } 0092 qCWarning(RUQOLA_LOG) << "Unknown status" << status; 0093 return QStringLiteral("unknown"); 0094 } 0095 0096 QString Utils::iconFromStatus(const QString &status) 0097 { 0098 if (status == QLatin1String("online")) { 0099 return QStringLiteral("user-online"); 0100 } else if (status == QLatin1String("busy")) { 0101 return QStringLiteral("user-busy"); 0102 } else if (status == QLatin1String("away")) { 0103 return QStringLiteral("user-away"); 0104 } else if (status == QLatin1String("offline")) { 0105 return QStringLiteral("user-offline"); 0106 } else { 0107 qCWarning(RUQOLA_LOG) << "Unknown status" << status; 0108 return QStringLiteral("unknown"); 0109 } 0110 } 0111 0112 User::PresenceStatus Utils::presenceStatusFromString(const QString &status) 0113 { 0114 if (status == QLatin1String("online")) { 0115 return User::PresenceStatus::PresenceOnline; 0116 } else if (status == QLatin1String("busy")) { 0117 return User::PresenceStatus::PresenceBusy; 0118 } else if (status == QLatin1String("away")) { 0119 return User::PresenceStatus::PresenceAway; 0120 } else if (status == QLatin1String("offline")) { 0121 return User::PresenceStatus::PresenceOffline; 0122 } else { 0123 qCDebug(RUQOLA_LOG) << "Problem with status " << status; 0124 return User::PresenceStatus::Unknown; 0125 } 0126 } 0127 0128 QString Utils::userIdFromDirectChannel(const QString &rid, const QString &userId) 0129 { 0130 QString newUserId = rid; 0131 newUserId.remove(userId); 0132 return newUserId; 0133 } 0134 0135 qint64 Utils::parseDate(const QString &key, const QJsonObject &o) 0136 { 0137 return o.value(key).toObject().value(QLatin1String("$date")).toDouble(-1); 0138 } 0139 0140 qint64 Utils::parseIsoDate(const QString &key, const QJsonObject &o) 0141 { 0142 if (o.contains(key)) { 0143 return QDateTime::fromString(o.value(key).toString(), Qt::ISODate).toMSecsSinceEpoch(); 0144 } else { 0145 return -1; 0146 } 0147 } 0148 0149 QString Utils::convertTextHeaders(const QString &str) 0150 { 0151 static const QRegularExpression useHeaders(QStringLiteral("(^|\\n)#.*\\s")); 0152 if (str.contains(useHeaders)) { 0153 static const QRegularExpression regularHeader1(QStringLiteral("(^|\\n)(#)\\s(.*)($|\\n)")); 0154 QString newStr = str; 0155 newStr = newStr.replace(regularHeader1, QStringLiteral("\\1<h1>\\3</h1>")); 0156 static const QRegularExpression regularHeader2(QStringLiteral("(^|\\n|</h\\d>)(##)\\s(.*)($|\\n)")); 0157 newStr = newStr.replace(regularHeader2, QStringLiteral("\\1<h2>\\3</h2>")); 0158 static const QRegularExpression regularHeader3(QStringLiteral("(^|\\n|</h\\d>)(###)\\s(.*)($|\\n)")); 0159 newStr = newStr.replace(regularHeader3, QStringLiteral("\\1<h3>\\3</h3>")); 0160 static const QRegularExpression regularHeader4(QStringLiteral("(^|\\n|</h\\d>)(####)\\s(.*)($|\\n)")); 0161 newStr = newStr.replace(regularHeader4, QStringLiteral("\\1<h4>\\3</h4>")); 0162 static const QRegularExpression regularHeader5(QStringLiteral("(^|\\n|</h\\d>)(#####)\\s(.*)($|\\n)")); 0163 newStr = newStr.replace(regularHeader5, QStringLiteral("\\1<h5>\\3</h5>")); 0164 static const QRegularExpression regularHeader6(QStringLiteral("(^|\\n|</h\\d>)(######)\\s(.*)($|\\n)")); 0165 newStr = newStr.replace(regularHeader6, QStringLiteral("\\1<h6>\\3</h6>")); 0166 return newStr; 0167 } 0168 return str; 0169 } 0170 0171 QString Utils::convertTextWithCheckMark(const QString &str) 0172 { 0173 static const QRegularExpression regularUnCheckMark(QStringLiteral("(^|\\n)-\\s\\[\\s\\]\\s")); 0174 static const QRegularExpression regularCheckMark(QStringLiteral("(^|\\n)-\\s\\[x]\\s")); 0175 QString newStr = str; 0176 newStr = newStr.replace(regularUnCheckMark, QStringLiteral("\\1:white_medium_square: ")); 0177 newStr = newStr.replace(regularCheckMark, QStringLiteral("\\1:ballot_box_with_check: ")); 0178 return newStr; 0179 } 0180 0181 QString Utils::convertTextWithUrl(const QString &str) 0182 { 0183 static const QRegularExpression regularExpressionAHref(QStringLiteral("<a href=\"(.*)\">(.*)</a>")); 0184 static const QRegularExpression regularExpressionCustomAHref(QStringLiteral("<a href=\"(.*)\\|(.*)\">(.*)</a>")); 0185 QString newStr; 0186 bool isRef = false; 0187 bool isUrl = false; 0188 bool isHasNewRef = false; 0189 QString url; 0190 QString references; 0191 for (int i = 0; i < str.length(); ++i) { 0192 const QChar ref = str.at(i); 0193 if (ref == QLatin1Char('[')) { 0194 if (isRef) { 0195 isRef = false; 0196 newStr += QLatin1Char('[') + references + QLatin1Char('['); 0197 references.clear(); 0198 } else { 0199 isRef = true; 0200 } 0201 #if 0 0202 } else if (isUrl && ref == QLatin1Char(']') && isHasNewRef) { 0203 isUrl = false; 0204 isRef = false; 0205 newStr += QStringLiteral("<a href=\'%1'>%2</a>").arg(url, references); 0206 references.clear(); 0207 url.clear(); 0208 #endif 0209 } else if (isRef && ref == QLatin1Char(']')) { 0210 isRef = false; 0211 if ((i == str.length() - 1) || (str.at(i + 1) != QLatin1Char('('))) { 0212 if (references.startsWith(QLatin1Char('<'))) { 0213 newStr += references.replace(regularExpressionCustomAHref, QStringLiteral("<a href=\"\\2\">\\1</a>")); 0214 } else { 0215 newStr += QLatin1Char('[') + references + QLatin1Char(']'); 0216 } 0217 references.clear(); 0218 } 0219 } else if (ref == QLatin1Char('(') && !references.isEmpty()) { 0220 isUrl = true; 0221 } else if (isUrl && ref == QLatin1Char(')') && !references.isEmpty()) { 0222 isUrl = false; 0223 // detect whether the string already contains HTML <a/> tags 0224 if (url.startsWith(QLatin1Char('<'))) { 0225 newStr += url.replace(regularExpressionAHref, QStringLiteral("<a href=\"\\1\">%1</a>").arg(references)); 0226 } else { 0227 newStr += QStringLiteral("<a href=\'%1'>%2</a>").arg(url, references); 0228 } 0229 references.clear(); 0230 url.clear(); 0231 #if 0 0232 } else if (ref == QLatin1Char('|') && !references.isEmpty()) { 0233 isUrl = true; 0234 isRef = false; 0235 isHasNewRef = true; 0236 #endif 0237 } else { 0238 if (isRef) { 0239 references += ref; 0240 } else if (isUrl) { 0241 url += ref; 0242 } else { 0243 newStr += ref; 0244 } 0245 } 0246 } 0247 if (isRef) { 0248 newStr += QLatin1Char('[') + references; 0249 } else if (isUrl) { 0250 newStr += QLatin1Char('[') + references + QLatin1String("](") + url; 0251 } else if (isHasNewRef) { 0252 if (!url.isEmpty() && !references.isEmpty()) { 0253 newStr += QStringLiteral("<a href=\'%1'>%2</a>").arg(url, references); 0254 } 0255 } 0256 return newStr; 0257 } 0258 0259 QJsonObject Utils::strToJsonObject(const QString &jsonString) 0260 { 0261 QJsonParseError jsonParseError; 0262 const auto doc = QJsonDocument::fromJson(jsonString.toLatin1(), &jsonParseError); 0263 0264 if (jsonParseError.error != QJsonParseError::NoError) { 0265 qCWarning(RUQOLA_LOG).nospace() << Q_FUNC_INFO << " Couldn't parse a valid JSON from argument: " << jsonString 0266 << "\n JSON parse error: " << jsonParseError.errorString(); 0267 return {}; 0268 } 0269 0270 if (!doc.isObject()) { 0271 qCWarning(RUQOLA_LOG) << Q_FUNC_INFO << "The JSON string argument is not a JSON object." << jsonString; 0272 return {}; 0273 } 0274 0275 return doc.object(); 0276 } 0277 0278 QJsonArray Utils::strToJsonArray(const QString &jsonString) 0279 { 0280 QJsonParseError jsonParseError; 0281 const auto doc = QJsonDocument::fromJson(jsonString.toLatin1(), &jsonParseError); 0282 0283 if (jsonParseError.error != QJsonParseError::NoError) { 0284 qCWarning(RUQOLA_LOG).nospace() << Q_FUNC_INFO << " Couldn't parse a valid JSON from argument: " << jsonString 0285 << "\n JSON parse error: " << jsonParseError.errorString(); 0286 return {}; 0287 } 0288 0289 if (!doc.isArray()) { 0290 qCWarning(RUQOLA_LOG) << Q_FUNC_INFO << "The JSON string argument is not a JSON array." << jsonString; 0291 return {}; 0292 } 0293 0294 return doc.array(); 0295 } 0296 0297 QByteArray Utils::convertSha256Password(const QString &pwd) 0298 { 0299 const QByteArray sha256pw = QCryptographicHash::hash(pwd.toUtf8(), QCryptographicHash::Sha256).toHex(); 0300 return sha256pw; 0301 } 0302 0303 QUrl Utils::avatarUrl(const QString &serverRcUrl, const AvatarInfo &avatarInfo) 0304 { 0305 if (serverRcUrl.isEmpty()) { 0306 return {}; 0307 } 0308 QString serverUrl = serverRcUrl; 0309 QString subFolder; 0310 switch (avatarInfo.avatarType) { 0311 case AvatarType::Room: 0312 subFolder = QStringLiteral("/room"); 0313 break; 0314 case AvatarType::Unknown: 0315 case AvatarType::User: 0316 case AvatarType::UserAndRoom: 0317 break; 0318 } 0319 subFolder += QLatin1Char('/') + avatarInfo.identifier; 0320 subFolder += QStringLiteral("?format=png"); 0321 if (!avatarInfo.etag.isEmpty()) { 0322 subFolder += QStringLiteral("&etag=%1").arg(avatarInfo.etag); 0323 } 0324 subFolder += QStringLiteral("&size=22"); 0325 if (!serverUrl.startsWith(QStringView(u"https://")) && !serverUrl.startsWith(QStringView(u"http://"))) { 0326 serverUrl.prepend(QStringLiteral("https://")); 0327 } 0328 return QUrl(serverUrl + QStringLiteral("/avatar") + subFolder); 0329 } 0330 0331 QDebug operator<<(QDebug d, const Utils::AvatarInfo &t) 0332 { 0333 d << "etag " << t.etag; 0334 d << "identifier " << t.identifier; 0335 d << "avatarType " << static_cast<int>(t.avatarType); 0336 return d; 0337 } 0338 0339 QString Utils::emojiFontName() 0340 { 0341 return TextEmoticonsCore::EmoticonUnicodeUtils::emojiFontName(); 0342 } 0343 0344 QString Utils::displaytextFromPresenceStatus(User::PresenceStatus status) 0345 { 0346 switch (status) { 0347 case User::PresenceStatus::PresenceOnline: 0348 return i18n("Online"); 0349 case User::PresenceStatus::PresenceBusy: 0350 return i18n("Busy"); 0351 case User::PresenceStatus::PresenceAway: 0352 return i18n("Away"); 0353 case User::PresenceStatus::PresenceOffline: 0354 return i18n("Offline"); 0355 case User::PresenceStatus::Unknown: 0356 return {}; 0357 } 0358 return {}; 0359 } 0360 0361 QString Utils::AvatarInfo::generateAvatarIdentifier() const 0362 { 0363 if (etag.isEmpty()) { 0364 return identifier; 0365 } else { 0366 return identifier + QLatin1Char('-') + etag; 0367 } 0368 }