diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 3dd51112..070d226f 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1934,11 +1934,268 @@ TimelineModel::formatPowerLevelEvent(const QString &id) if (!event) return QString(); - QString user = QString::fromStdString(event->sender); - QString name = utils::replaceEmoji(displayName(user)); + mtx::events::StateEvent *prevEvent = nullptr; + if (!event->unsigned_data.replaces_state.empty()) { + auto tempPrevEvent = events.get(event->unsigned_data.replaces_state, event->event_id); + if (tempPrevEvent) { + prevEvent = + std::get_if>(tempPrevEvent); + } + } - // TODO: power levels rendering is actually a bit complex. work on this later. - return tr("%1 has changed the room's permissions.").arg(name); + QString user = QString::fromStdString(event->sender); + QString sender_name = utils::replaceEmoji(displayName(user)); + // Get the rooms levels for redactions and powerlevel changes to determine "Administrator" and + // "Moderator" powerlevels. + auto administrator_power_level = event->content.state_level("m.room.power_levels"); + auto moderator_power_level = event->content.redact; + auto default_powerlevel = event->content.users_default; + if (!prevEvent) + return tr("%1 has changed the room's permissions.").arg(sender_name); + + auto calc_affected = [&event, + &prevEvent](int64_t newPowerlevelSetting) -> std::pair { + QStringList affected{}; + auto numberOfAffected = 0; + // We do only compare to people with explicit PL. Usually others are not going to be + // affected either way and this is cheaper to iterate over. + for (auto const &[mxid, currentPowerlevel] : event->content.users) { + if (currentPowerlevel == newPowerlevelSetting && + prevEvent->content.user_level(mxid) < newPowerlevelSetting) { + numberOfAffected++; + if (numberOfAffected <= 2) { + affected.push_back(QString::fromStdString(mxid)); + } + } + } + return {affected, numberOfAffected}; + }; + + QStringList resultingMessage{}; + // These affect only a few people. Therefor we can print who is affected. + if (event->content.kick != prevEvent->content.kick) { + auto default_message = tr("%1 has changed the room's kick powerlevel from %2 to %3.") + .arg(sender_name) + .arg(prevEvent->content.kick) + .arg(event->content.kick); + + // We only calculate affected users if we change to a level above the default users PL + // to not accidentally have a DoS vector + if (event->content.kick > default_powerlevel) { + auto [affected, number_of_affected] = calc_affected(event->content.kick); + + if (number_of_affected != 0) { + auto true_affected_rest = number_of_affected - affected.size(); + if (number_of_affected > 1) { + resultingMessage.append( + default_message + QStringLiteral(" ") + + tr("%n member(s) can now kick room members.", nullptr, true_affected_rest)); + } else if (number_of_affected == 1) { + resultingMessage.append( + default_message + QStringLiteral(" ") + + tr("%1 can now kick room members.") + .arg(utils::replaceEmoji(displayName(affected.at(0))))); + } + } else { + resultingMessage.append(default_message); + } + } else { + resultingMessage.append(default_message); + } + } + + if (event->content.redact != prevEvent->content.redact) { + auto default_message = tr("%1 has changed the room's redact powerlevel from %2 to %3.") + .arg(sender_name) + .arg(prevEvent->content.redact) + .arg(event->content.redact); + + // We only calculate affected users if we change to a level above the default users PL + // to not accidentally have a DoS vector + if (event->content.redact > default_powerlevel) { + auto [affected, number_of_affected] = calc_affected(event->content.redact); + + if (number_of_affected != 0) { + auto true_affected_rest = number_of_affected - affected.size(); + if (number_of_affected > 1) { + resultingMessage.append( + default_message + QStringLiteral(" ") + + tr("%n member(s) can now redact room messages.", nullptr, true_affected_rest)); + } else if (number_of_affected == 1) { + resultingMessage.append( + default_message + QStringLiteral(" ") + + tr("%1 can now redact room messages.") + .arg(utils::replaceEmoji(displayName(affected.at(0))))); + } + } else { + resultingMessage.append(default_message); + } + } else { + resultingMessage.append(default_message); + } + } + + if (event->content.ban != prevEvent->content.ban) { + auto default_message = tr("%1 has changed the room's ban powerlevel from %2 to %3.") + .arg(sender_name) + .arg(prevEvent->content.ban) + .arg(event->content.ban); + + // We only calculate affected users if we change to a level above the default users PL + // to not accidentally have a DoS vector + if (event->content.ban > default_powerlevel) { + auto [affected, number_of_affected] = calc_affected(event->content.ban); + + if (number_of_affected != 0) { + auto true_affected_rest = number_of_affected - affected.size(); + if (number_of_affected > 1) { + resultingMessage.append( + default_message + QStringLiteral(" ") + + tr("%n member(s) can now ban room members.", nullptr, true_affected_rest)); + } else if (number_of_affected == 1) { + resultingMessage.append( + default_message + QStringLiteral(" ") + + tr("%1 can now ban room members.") + .arg(utils::replaceEmoji(displayName(affected.at(0))))); + } + } else { + resultingMessage.append(default_message); + } + } else { + resultingMessage.append(default_message); + } + } + + if (event->content.state_default != prevEvent->content.state_default) { + auto default_message = + tr("%1 has changed the room's state_default powerlevel from %2 to %3.") + .arg(sender_name) + .arg(prevEvent->content.state_default) + .arg(event->content.state_default); + + // We only calculate affected users if we change to a level above the default users PL + // to not accidentally have a DoS vector + if (event->content.state_default > default_powerlevel) { + auto [affected, number_of_affected] = calc_affected(event->content.kick); + + if (number_of_affected != 0) { + auto true_affected_rest = number_of_affected - affected.size(); + if (number_of_affected > 1) { + resultingMessage.append( + default_message + QStringLiteral(" ") + + tr("%n member(s) can now send state events.", nullptr, true_affected_rest)); + } else if (number_of_affected == 1) { + resultingMessage.append( + default_message + QStringLiteral(" ") + + tr("%1 can now send state events.") + .arg(utils::replaceEmoji(displayName(affected.at(0))))); + } + } else { + resultingMessage.append(default_message); + } + } else { + resultingMessage.append(default_message); + } + } + + // These affect potentially the whole room. We there for do not calculate who gets affected + // by this to prevent huge lists of people. + if (event->content.invite != prevEvent->content.invite) { + resultingMessage.append(tr("%1 has changed the room's invite powerlevel from %2 to %3.") + .arg(sender_name, + QString::number(prevEvent->content.invite), + QString::number(event->content.invite))); + } + + if (event->content.events_default != prevEvent->content.events_default) { + if ((event->content.events_default > default_powerlevel) && + prevEvent->content.events_default <= default_powerlevel) { + resultingMessage.append( + tr("%1 has changed the room's events_default powerlevel from %2 to %3. New " + "users can now not send any events.") + .arg(sender_name, + QString::number(prevEvent->content.events_default), + QString::number(event->content.events_default))); + } else if ((event->content.events_default < prevEvent->content.events_default) && + (event->content.events_default < default_powerlevel) && + (prevEvent->content.events_default > default_powerlevel)) { + resultingMessage.append( + tr("%1 has changed the room's events_default powerlevel from %2 to %3. New " + "users can now send events that are not otherwise restricted.") + .arg(sender_name, + QString::number(prevEvent->content.events_default), + QString::number(event->content.events_default))); + } else { + resultingMessage.append( + tr("%1 has changed the room's events_default powerlevel from %2 to %3.") + .arg(sender_name, + QString::number(prevEvent->content.events_default), + QString::number(event->content.events_default))); + } + } + + // Compare if a Powerlevel of a user changed + for (auto const &[mxid, powerlevel] : event->content.users) { + auto nameOfChangedUser = utils::replaceEmoji(displayName(QString::fromStdString(mxid))); + if (prevEvent->content.user_level(mxid) != powerlevel) { + if (powerlevel >= administrator_power_level) { + resultingMessage.append(tr("%1 has made %2 an administrator of this room.") + .arg(sender_name, nameOfChangedUser)); + } else if (powerlevel >= moderator_power_level && + powerlevel > prevEvent->content.user_level(mxid)) { + resultingMessage.append(tr("%1 has made %2 a moderator of this room.") + .arg(sender_name, nameOfChangedUser)); + } else if (powerlevel >= moderator_power_level && + powerlevel < prevEvent->content.user_level(mxid)) { + resultingMessage.append(tr("%1 has downgraded %2 to moderator of this room.") + .arg(sender_name, nameOfChangedUser)); + } else { + resultingMessage.append(tr("%1 has changed the powerlevel of %2 from %3 to %4.") + .arg(sender_name, + nameOfChangedUser, + QString::number(prevEvent->content.user_level(mxid)), + QString::number(powerlevel))); + } + } + } + + // Handle added/removed/changed event type + for (auto const &[event_type, powerlevel] : event->content.events) { + auto prev_not_present = + prevEvent->content.events.find(event_type) == prevEvent->content.events.end(); + + if (prev_not_present || prevEvent->content.events.at(event_type) != powerlevel) { + if (powerlevel >= administrator_power_level) { + resultingMessage.append(tr("%1 allowed only administrators to send \"%2\".") + .arg(sender_name, QString::fromStdString(event_type))); + } else if (powerlevel >= moderator_power_level) { + resultingMessage.append(tr("%1 allowed only moderators to send \"%2\".") + .arg(sender_name, QString::fromStdString(event_type))); + } else if (powerlevel == default_powerlevel) { + resultingMessage.append(tr("%1 allowed everyone to send \"%2\".") + .arg(sender_name, QString::fromStdString(event_type))); + } else if (prev_not_present) { + resultingMessage.append( + tr("%1 has changed the powerlevel of event type \"%2\" from the default to %3.") + .arg(sender_name, + QString::fromStdString(event_type), + QString::number(powerlevel))); + } else { + resultingMessage.append( + tr("%1 has changed the powerlevel of event type \"%2\" from %3 to %4.") + .arg(sender_name, + QString::fromStdString(event_type), + QString::number(prevEvent->content.events.at(event_type)), + QString::number(powerlevel))); + } + } + } + + if (!resultingMessage.isEmpty()) { + return resultingMessage.join("
"); + } else { + return tr("%1 has changed the room's permissions.").arg(sender_name); + } } QVariantMap