summary refs log tree commit diff
path: root/src/AliasEditModel.cpp
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2022-07-08 17:28:28 +0200
committerNicolas Werner <nicolas.werner@hotmail.de>2022-07-08 17:28:28 +0200
commit931855441a12b328afc0aecc1464af927199808b (patch)
tree38248e3f7eb1790e9f8bb7aaefb88c4584466808 /src/AliasEditModel.cpp
parentFix users with @room in the name pinging the whole room when replied to (diff)
downloadnheko-931855441a12b328afc0aecc1464af927199808b.tar.xz
Allow editing aliases
Diffstat (limited to 'src/AliasEditModel.cpp')
-rw-r--r--src/AliasEditModel.cpp336
1 files changed, 336 insertions, 0 deletions
diff --git a/src/AliasEditModel.cpp b/src/AliasEditModel.cpp
new file mode 100644
index 00000000..1ca7f5e6
--- /dev/null
+++ b/src/AliasEditModel.cpp
@@ -0,0 +1,336 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "AliasEditModel.h"
+
+#include <QSharedPointer>
+
+#include <set>
+
+#include <mtx/responses/common.hpp>
+
+#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<mtx::events::state::CanonicalAlias>(room_id)
+                 .value_or(mtx::events::StateEvent<mtx::events::state::CanonicalAlias>{})
+                 .content)
+  , canSendStateEvent(
+      Permissions(QString::fromStdString(rid)).canChange(qml_mtx_events::CanonicalAlias))
+{
+    std::set<std::string> 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{aliasEvent.alias, false, true, false});
+            seen_aliases.insert(aliasEvent.alias);
+        }
+    }
+
+    for (const auto &alias : aliases) {
+        fetchAliasesStatus(alias.alias);
+    }
+    fetchPublishedAliases();
+}
+
+void
+AliasEditingModel::fetchPublishedAliases()
+{
+    auto job = QSharedPointer<FetchPublishedAliasesJob>::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<FetchPublishedAliasesJob>::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<int, QByteArray>
+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 : 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<FetchPublishedAliasesJob>::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 (qsizetype 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<FetchPublishedAliasesJob>::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 (qsizetype 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<std::string> advAliases)
+{
+    for (const auto &advAlias : advAliases) {
+        bool found = false;
+        for (qsizetype 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)));
+          }
+      });
+}