// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2023 Nheko Contributors // // SPDX-License-Identifier: GPL-3.0-or-later #include "AliasEditModel.h" #include #include #include #include "Cache.h" #include "Cache_p.h" #include "ChatPage.h" #include "Logging.h" #include "MatrixClient.h" #include "timeline/Permissions.h" #include "timeline/TimelineModel.h" AliasEditingModel::AliasEditingModel(const std::string &rid, QObject *parent) : QAbstractListModel(parent) , room_id(rid) , aliasEvent(cache::client() ->getStateEvent(room_id) .value_or(mtx::events::StateEvent{}) .content) , canSendStateEvent( Permissions(QString::fromStdString(rid)).canChange(qml_mtx_events::CanonicalAlias)) { std::set seen_aliases; if (!aliasEvent.alias.empty()) { aliases.push_back(Entry{aliasEvent.alias, true, true, false}); seen_aliases.insert(aliasEvent.alias); } for (const auto &alias : aliasEvent.alt_aliases) { if (!seen_aliases.count(alias)) { aliases.push_back(Entry{alias, false, true, false}); seen_aliases.insert(alias); } } for (const auto &alias : qAsConst(aliases)) { fetchAliasesStatus(alias.alias); } fetchPublishedAliases(); } void AliasEditingModel::fetchPublishedAliases() { auto job = QSharedPointer::create(); connect(job.data(), &FetchPublishedAliasesJob::advertizedAliasesFetched, this, &AliasEditingModel::updatePublishedAliases); http::client()->list_room_aliases( room_id, [job](const mtx::responses::Aliases &aliasesFetched, mtx::http::RequestErr) { emit job->advertizedAliasesFetched(std::move(aliasesFetched.aliases)); }); } void AliasEditingModel::fetchAliasesStatus(const std::string &alias) { auto job = QSharedPointer::create(); connect( job.data(), &FetchPublishedAliasesJob::aliasFetched, this, &AliasEditingModel::updateAlias); http::client()->resolve_room_alias( alias, [job, alias](const mtx::responses::RoomId &roomIdFetched, mtx::http::RequestErr e) { if (!e) emit job->aliasFetched(alias, std::move(roomIdFetched.room_id)); }); } QHash AliasEditingModel::roleNames() const { return { {Name, "name"}, {IsPublished, "isPublished"}, {IsCanonical, "isCanonical"}, {IsAdvertized, "isAdvertized"}, }; } QVariant AliasEditingModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= aliases.size()) return {}; const auto &entry = aliases.at(index.row()); switch (role) { case Name: return QString::fromStdString(entry.alias); case IsPublished: return entry.published; case IsCanonical: return entry.canonical; case IsAdvertized: return entry.advertized; } return {}; } bool AliasEditingModel::deleteAlias(int row) { if (row < 0 || row >= aliases.size() || aliases.at(row).alias.empty()) return false; auto alias = aliases.at(row); beginRemoveRows(QModelIndex(), row, row); aliases.remove(row); endRemoveRows(); if (alias.published) http::client()->delete_room_alias(alias.alias, [alias](mtx::http::RequestErr e) { if (e) { nhlog::net()->error("Failed to delete {}: {}", alias.alias, *e); ChatPage::instance()->showNotification( tr("Failed to unpublish alias %1: %2") .arg(QString::fromStdString(alias.alias), QString::fromStdString(e->matrix_error.error))); } }); if (aliasEvent.alias == alias.alias) aliasEvent.alias.clear(); for (size_t i = 0; i < aliasEvent.alt_aliases.size(); i++) { if (aliasEvent.alt_aliases[i] == alias.alias) { aliasEvent.alt_aliases.erase(aliasEvent.alt_aliases.begin() + i); break; } } return true; } void AliasEditingModel::addAlias(QString newAlias) { const auto aliasStr = newAlias.toStdString(); for (const auto &e : qAsConst(aliases)) { if (e.alias == aliasStr) { return; } } beginInsertRows(QModelIndex(), aliases.length(), aliases.length()); if (aliasEvent.alias.empty()) aliasEvent.alias = aliasStr; else aliasEvent.alt_aliases.push_back(aliasStr); aliases.push_back( Entry{aliasStr, aliasEvent.alias.empty() && canSendStateEvent, canSendStateEvent, false}); endInsertRows(); auto job = QSharedPointer::create(); connect( job.data(), &FetchPublishedAliasesJob::aliasFetched, this, &AliasEditingModel::updateAlias); auto room = room_id; http::client()->add_room_alias( aliasStr, room_id, [job, aliasStr, room](mtx::http::RequestErr e) { if (e) { nhlog::net()->error("Failed to publish {}: {}", aliasStr, *e); ChatPage::instance()->showNotification( tr("Failed to unpublish alias %1: %2") .arg(QString::fromStdString(aliasStr), QString::fromStdString(e->matrix_error.error))); emit job->aliasFetched(aliasStr, ""); } else { emit job->aliasFetched(aliasStr, room); } }); } void AliasEditingModel::makeCanonical(int row) { if (!canSendStateEvent || row < 0 || row >= aliases.size() || aliases.at(row).alias.empty()) return; auto moveAlias = aliases.at(row).alias; if (!aliasEvent.alias.empty()) { for (int i = 0; i < aliases.size(); i++) { if (moveAlias == aliases[i].alias) { if (aliases[i].canonical) { aliases[i].canonical = false; aliasEvent.alt_aliases.push_back(aliasEvent.alias); emit dataChanged(index(i), index(i), {IsCanonical}); } break; } } } aliasEvent.alias = moveAlias; for (auto i = aliasEvent.alt_aliases.begin(); i != aliasEvent.alt_aliases.end(); ++i) { if (*i == moveAlias) { aliasEvent.alt_aliases.erase(i); break; } } aliases[row].canonical = true; aliases[row].advertized = true; emit dataChanged(index(row), index(row), {IsCanonical, IsAdvertized}); } void AliasEditingModel::togglePublish(int row) { if (row < 0 || row >= aliases.size() || aliases.at(row).alias.empty()) return; auto aliasStr = aliases[row].alias; auto job = QSharedPointer::create(); connect( job.data(), &FetchPublishedAliasesJob::aliasFetched, this, &AliasEditingModel::updateAlias); auto room = room_id; if (!aliases[row].published) http::client()->add_room_alias( aliasStr, room_id, [job, aliasStr, room](mtx::http::RequestErr e) { if (e) { nhlog::net()->error("Failed to publish {}: {}", aliasStr, *e); ChatPage::instance()->showNotification( tr("Failed to unpublish alias %1: %2") .arg(QString::fromStdString(aliasStr), QString::fromStdString(e->matrix_error.error))); emit job->aliasFetched(aliasStr, ""); } else { emit job->aliasFetched(aliasStr, room); } }); else http::client()->delete_room_alias(aliasStr, [job, aliasStr, room](mtx::http::RequestErr e) { if (e) { nhlog::net()->error("Failed to unpublish {}: {}", aliasStr, *e); ChatPage::instance()->showNotification( tr("Failed to unpublish alias %1: %2") .arg(QString::fromStdString(aliasStr), QString::fromStdString(e->matrix_error.error))); emit job->aliasFetched(aliasStr, room); } else { emit job->aliasFetched(aliasStr, ""); } }); } void AliasEditingModel::toggleAdvertize(int row) { if (!canSendStateEvent || row < 0 || row >= aliases.size() || aliases.at(row).alias.empty()) return; auto &moveAlias = aliases[row]; if (aliasEvent.alias == moveAlias.alias) { moveAlias.canonical = false; moveAlias.advertized = false; aliasEvent.alias.clear(); emit dataChanged(index(row), index(row), {IsAdvertized, IsCanonical}); } else if (moveAlias.advertized) { for (auto i = aliasEvent.alt_aliases.begin(); i != aliasEvent.alt_aliases.end(); ++i) { if (*i == moveAlias.alias) { aliasEvent.alt_aliases.erase(i); moveAlias.advertized = false; emit dataChanged(index(row), index(row), {IsAdvertized}); break; } } } else { aliasEvent.alt_aliases.push_back(moveAlias.alias); moveAlias.advertized = true; emit dataChanged(index(row), index(row), {IsAdvertized}); } } void AliasEditingModel::updateAlias(std::string alias, std::string target) { for (int i = 0; i < aliases.size(); i++) { auto &e = aliases[i]; if (e.alias == alias) { e.published = (target == room_id); emit dataChanged(index(i), index(i), {IsPublished}); } } } void AliasEditingModel::updatePublishedAliases(std::vector advAliases) { for (const auto &advAlias : advAliases) { bool found = false; for (int i = 0; i < aliases.size(); i++) { auto &alias = aliases[i]; if (alias.alias == advAlias) { alias.published = true; emit dataChanged(index(i), index(i), {IsPublished}); found = true; break; } } if (!found) { beginInsertRows(QModelIndex(), aliases.size(), aliases.size()); aliases.push_back(Entry{advAlias, false, false, true}); endInsertRows(); } } } void AliasEditingModel::commit() { if (!canSendStateEvent) return; http::client()->send_state_event( room_id, aliasEvent, [](const mtx::responses::EventId &, mtx::http::RequestErr e) { if (e) { nhlog::net()->error("Failed to send Alias event: {}", *e); ChatPage::instance()->showNotification( tr("Failed to update aliases: %1") .arg(QString::fromStdString(e->matrix_error.error))); } }); }