File indexing completed on 2024-10-06 07:36:09

0001 // SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
0002 // SPDX-License-Identifier: LGPL-2.0-or-later
0003 
0004 #include "pollhandler.h"
0005 
0006 #include "neochatroom.h"
0007 
0008 #include <Quotient/csapi/relations.h>
0009 #include <Quotient/events/roompowerlevelsevent.h>
0010 
0011 #include <algorithm>
0012 
0013 using namespace Quotient;
0014 
0015 PollHandler::PollHandler(NeoChatRoom *room, const Quotient::PollStartEvent *pollStartEvent)
0016     : QObject(room)
0017     , m_pollStartEvent(pollStartEvent)
0018 {
0019     if (room != nullptr && m_pollStartEvent != nullptr) {
0020         connect(room, &NeoChatRoom::aboutToAddNewMessages, this, &PollHandler::updatePoll);
0021         checkLoadRelations();
0022     }
0023 }
0024 
0025 void PollHandler::updatePoll(Quotient::RoomEventsRange events)
0026 {
0027     // This function will never be called if the PollHandler was not initialized with
0028     // a NeoChatRoom as parent and a PollStartEvent so no need to null check.
0029     auto room = dynamic_cast<NeoChatRoom *>(parent());
0030     for (const auto &event : events) {
0031         if (event->is<PollEndEvent>()) {
0032             auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
0033             if (!plEvent) {
0034                 continue;
0035             }
0036             auto userPl = plEvent->powerLevelForUser(event->senderId());
0037             if (event->senderId() == m_pollStartEvent->senderId() || userPl >= plEvent->redact()) {
0038                 m_hasEnded = true;
0039                 m_endedTimestamp = event->originTimestamp();
0040                 Q_EMIT hasEndedChanged();
0041             }
0042         }
0043         if (event->is<PollResponseEvent>()) {
0044             handleAnswer(event->contentJson(), event->senderId(), event->originTimestamp());
0045         }
0046         if (event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
0047             && event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.replace"_ls
0048             && event->contentPart<QJsonObject>("m.relates_to"_ls)["event_id"_ls].toString() == m_pollStartEvent->id()) {
0049             Q_EMIT questionChanged();
0050             Q_EMIT optionsChanged();
0051         }
0052     }
0053 }
0054 
0055 void PollHandler::checkLoadRelations()
0056 {
0057     // This function will never be called if the PollHandler was not initialized with
0058     // a NeoChatRoom as parent and a PollStartEvent so no need to null check.
0059     auto room = dynamic_cast<NeoChatRoom *>(parent());
0060     m_maxVotes = m_pollStartEvent->maxSelections();
0061     auto job = room->connection()->callApi<GetRelatingEventsJob>(room->id(), m_pollStartEvent->id());
0062     connect(job, &BaseJob::success, this, [this, job, room]() {
0063         for (const auto &event : job->chunk()) {
0064             if (event->is<PollEndEvent>()) {
0065                 auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
0066                 if (!plEvent) {
0067                     continue;
0068                 }
0069                 auto userPl = plEvent->powerLevelForUser(event->senderId());
0070                 if (event->senderId() == m_pollStartEvent->senderId() || userPl >= plEvent->redact()) {
0071                     m_hasEnded = true;
0072                     m_endedTimestamp = event->originTimestamp();
0073                     Q_EMIT hasEndedChanged();
0074                 }
0075             }
0076             if (event->is<PollResponseEvent>()) {
0077                 handleAnswer(event->contentJson(), event->senderId(), event->originTimestamp());
0078             }
0079         }
0080     });
0081 }
0082 
0083 void PollHandler::handleAnswer(const QJsonObject &content, const QString &sender, QDateTime timestamp)
0084 {
0085     if (timestamp > m_answerTimestamps[sender] && (!m_hasEnded || timestamp < m_endedTimestamp)) {
0086         m_answerTimestamps[sender] = timestamp;
0087         m_answers[sender] = {};
0088         int i = 0;
0089         for (const auto &answer : content["org.matrix.msc3381.poll.response"_ls]["answers"_ls].toArray()) {
0090             auto array = m_answers[sender].toArray();
0091             array.insert(0, answer);
0092             m_answers[sender] = array;
0093             i++;
0094             if (i == m_maxVotes) {
0095                 break;
0096             }
0097         }
0098         for (const auto &key : m_answers.keys()) {
0099             if (m_answers[key].toArray().isEmpty()) {
0100                 m_answers.remove(key);
0101             }
0102         }
0103     }
0104     Q_EMIT answersChanged();
0105 }
0106 
0107 QString PollHandler::question() const
0108 {
0109     if (m_pollStartEvent == nullptr) {
0110         return {};
0111     }
0112     return m_pollStartEvent->contentPart<QJsonObject>("org.matrix.msc3381.poll.start"_ls)["question"_ls].toObject()["body"_ls].toString();
0113 }
0114 
0115 QJsonArray PollHandler::options() const
0116 {
0117     if (m_pollStartEvent == nullptr) {
0118         return {};
0119     }
0120     return m_pollStartEvent->contentPart<QJsonObject>("org.matrix.msc3381.poll.start"_ls)["answers"_ls].toArray();
0121 }
0122 
0123 QJsonObject PollHandler::answers() const
0124 {
0125     return m_answers;
0126 }
0127 
0128 QJsonObject PollHandler::counts() const
0129 {
0130     QJsonObject counts;
0131     for (const auto &answer : m_answers) {
0132         for (const auto &id : answer.toArray()) {
0133             counts[id.toString()] = counts[id.toString()].toInt() + 1;
0134         }
0135     }
0136     return counts;
0137 }
0138 
0139 QString PollHandler::kind() const
0140 {
0141     if (m_pollStartEvent == nullptr) {
0142         return {};
0143     }
0144     return m_pollStartEvent->contentPart<QJsonObject>("org.matrix.msc3381.poll.start"_ls)["kind"_ls].toString();
0145 }
0146 
0147 void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId)
0148 {
0149     Q_ASSERT(eventId.length() > 0);
0150     Q_ASSERT(answerId.length() > 0);
0151     auto room = dynamic_cast<NeoChatRoom *>(parent());
0152     if (room == nullptr) {
0153         qWarning() << "PollHandler is empty, cannot send an answer.";
0154         return;
0155     }
0156     QStringList ownAnswers;
0157     for (const auto &answer : m_answers[room->localUser()->id()].toArray()) {
0158         ownAnswers += answer.toString();
0159     }
0160     if (ownAnswers.contains(answerId)) {
0161         ownAnswers.erase(std::remove_if(ownAnswers.begin(), ownAnswers.end(), [answerId](const auto &it) {
0162             return answerId == it;
0163         }));
0164     } else {
0165         while (ownAnswers.size() >= m_maxVotes) {
0166             ownAnswers.pop_front();
0167         }
0168         ownAnswers.insert(0, answerId);
0169     }
0170 
0171     auto response = new PollResponseEvent(eventId, ownAnswers);
0172     handleAnswer(response->contentJson(), room->localUser()->id(), QDateTime::currentDateTime());
0173     room->postEvent(response);
0174 }
0175 
0176 bool PollHandler::hasEnded() const
0177 {
0178     return m_hasEnded;
0179 }
0180 
0181 int PollHandler::answerCount() const
0182 {
0183     return m_answers.size();
0184 }
0185 
0186 #include "moc_pollhandler.cpp"