From 60ff46b0127c01ab551f4fc468bbd549cfb49985 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 26 Mar 2026 21:47:29 +0300 Subject: [PATCH] Added support of service messages for polls. --- Telegram/Resources/langs/lang.strings | 4 + .../export/data/export_data_types.cpp | 18 ++++ .../export/data/export_data_types.h | 10 ++ .../export/output/export_output_html.cpp | 8 ++ .../export/output/export_output_json.cpp | 8 ++ Telegram/SourceFiles/history/history.cpp | 37 +++++++ Telegram/SourceFiles/history/history_item.cpp | 97 ++++++++++++++++++- Telegram/SourceFiles/history/history_item.h | 2 + .../history/history_item_components.h | 12 +++ .../view/history_view_service_message.cpp | 4 + 10 files changed, 198 insertions(+), 2 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a0a5c8cadb..73e6933d8a 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2528,6 +2528,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_todo_marked_not_done_self" = "You marked {tasks} as not done."; "lng_action_todo_added" = "{from} added {tasks} to the list."; "lng_action_todo_added_self" = "You added {tasks} to the list."; +"lng_action_poll_added_answer" = "{from} added \"{option}\" to the poll."; +"lng_action_poll_added_answer_self" = "You added \"{option}\" to the poll."; +"lng_action_poll_deleted_answer" = "{from} removed \"{option}\" from the poll."; +"lng_action_poll_deleted_answer_self" = "You removed \"{option}\" from the poll."; "lng_action_todo_tasks_fallback#one" = "task"; "lng_action_todo_tasks_fallback#other" = "{count} tasks"; "lng_action_todo_tasks_and_one" = "{tasks}, {task}"; diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index af71fa99f6..48d8c424de 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1803,6 +1803,8 @@ ServiceAction ParseServiceAction( | ranges::views::transform(ParseTodoListItem) | ranges::to_vector, }; + }, [&](const MTPDmessageActionPollAppendAnswer &data) { + result.content = ActionPollAppendAnswer{}; }, [&](const MTPDmessageActionSuggestedPostApproval &data) { result.content = ActionSuggestedPostApproval{ .rejectComment = data.vreject_comment().value_or_empty(), @@ -1879,7 +1881,23 @@ ServiceAction ParseServiceAction( content.botId = data.vbot_id().v; result.content = content; }, [&](const MTPDmessageActionPollAppendAnswer &data) { + auto content = ActionPollAppendAnswer(); + data.vanswer().match([&](const MTPDpollAnswer &answer) { + content.option = ParseString(answer.vtext().match( + [](const MTPDtextWithEntities &d) { + return d.vtext(); + })); + }, [](const auto &) {}); + result.content = content; }, [&](const MTPDmessageActionPollDeleteAnswer &data) { + auto content = ActionPollDeleteAnswer(); + data.vanswer().match([&](const MTPDpollAnswer &answer) { + content.option = ParseString(answer.vtext().match( + [](const MTPDtextWithEntities &d) { + return d.vtext(); + })); + }, [](const auto &) {}); + result.content = content; }, [](const MTPDmessageActionEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index fbeac42816..2ce4ae30d9 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -718,6 +718,14 @@ struct ActionTodoAppendTasks { std::vector items; }; +struct ActionPollAppendAnswer { + Utf8String option; +}; + +struct ActionPollDeleteAnswer { + Utf8String option; +}; + struct ActionSuggestedPostApproval { Utf8String rejectComment; TimeId scheduleDate = 0; @@ -809,6 +817,8 @@ struct ServiceAction { ActionPaidMessagesPrice, ActionTodoCompletions, ActionTodoAppendTasks, + ActionPollAppendAnswer, + ActionPollDeleteAnswer, ActionSuggestedPostApproval, ActionSuggestedPostSuccess, ActionSuggestedPostRefund, diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 877d734604..34b97d2c94 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1501,6 +1501,14 @@ auto HtmlWriter::Wrap::pushMessage( + """); } return serviceFrom + " added tasks: " + tasks.join(", "); + }, [&](const ActionPollAppendAnswer &data) { + return serviceFrom + " added "" + + data.option + + "" to the poll."; + }, [&](const ActionPollDeleteAnswer &data) { + return serviceFrom + " removed "" + + data.option + + "" from the poll."; }, [&](const ActionSuggestedPostApproval &data) { return serviceFrom + (data.rejected ? " rejected " : " approved ") diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index 3f53d6d186..ec6fc54cb2 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -711,6 +711,14 @@ QByteArray SerializeMessage( return result; }) | ranges::to_vector; pushBare("items", SerializeArray(context, items)); + }, [&](const ActionPollAppendAnswer &data) { + pushActor(); + pushAction("poll_append_answer"); + push("option", data.option); + }, [&](const ActionPollDeleteAnswer &data) { + pushActor(); + pushAction("poll_delete_answer"); + push("option", data.option); }, [&](const ActionSuggestedPostApproval &data) { pushActor(); pushAction("process_suggested_post"); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 9cd279ee15..21afd20cef 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -49,6 +49,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_histories.h" #include "data/data_history_messages.h" +#include "api/api_text_entities.h" +#include "data/data_poll.h" #include "data/data_todo_list.h" #include "lang/lang_keys.h" #include "apiwrap.h" @@ -1429,6 +1431,41 @@ void History::applyServiceChanges( } } } + }, [&](const MTPDmessageActionPollAppendAnswer &data) { + if (const auto append = item->Get()) { + const auto list = append->msg + ? append->msg + : owner().message(peer, append->msgId); + if (const auto media = list ? list->media() : nullptr) { + if (const auto poll = media->poll()) { + data.vanswer().match([&](const MTPDpollAnswer &answer) { + auto parsed = PollAnswer(); + parsed.option = answer.voption().v; + parsed.text = Api::ParseTextWithEntities( + &session(), + answer.vtext()); + if (!poll->answerByOption(parsed.option)) { + poll->answers.push_back(std::move(parsed)); + } + }, [](const auto &) {}); + } + } + } + }, [&](const MTPDmessageActionPollDeleteAnswer &data) { + if (const auto del = item->Get()) { + const auto list = del->msg + ? del->msg + : owner().message(peer, del->msgId); + if (const auto media = list ? list->media() : nullptr) { + if (const auto poll = media->poll()) { + const auto option = del->answer.option; + auto &answers = poll->answers; + answers.erase( + ranges::remove(answers, option, &PollAnswer::option), + end(answers)); + } + } + } }, [&](const MTPDmessageActionStarGift &data) { if (data.is_auction_acquired() && data.vto_id()) { const auto to = peer->owner().peer(peerFromMTP(*data.vto_id())); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 40a6e008dc..25598b7e1b 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -866,6 +866,8 @@ HistoryServiceDependentData *HistoryItem::GetServiceDependentData() { return done; } else if (const auto append = Get()) { return append; + } else if (const auto poll = Get()) { + return poll; } else if (const auto decision = Get()) { return decision; } else if (const auto finish = Get()) { @@ -931,6 +933,8 @@ void HistoryItem::updateDependentServiceText() { updateServiceText(prepareTodoCompletionsText()); } else if (Has()) { updateServiceText(prepareTodoAppendTasksText()); + } else if (Has()) { + updateServiceText(preparePollAppendAnswerText()); } } @@ -947,6 +951,7 @@ void HistoryItem::updateServiceDependent(bool force) { if (!dependent->lnk) { auto todoItemId = 0; + auto pollOption = QByteArray(); if (const auto done = Get()) { const auto &items = !done->completed.empty() ? done->completed @@ -958,6 +963,10 @@ void HistoryItem::updateServiceDependent(bool force) { if (append->list.size() == 1) { todoItemId = append->list.front().id; } + } else if (const auto pa = Get()) { + pollOption = pa->answer.option; + } else if (const auto pd = Get()) { + pollOption = pd->answer.option; } dependent->lnk = JumpToMessageClickHandler( (dependent->peerId @@ -965,7 +974,7 @@ void HistoryItem::updateServiceDependent(bool force) { : _history->peer), dependent->msgId, fullId(), - { .todoItemId = todoItemId }); + { .todoItemId = todoItemId, .pollOption = pollOption }); } auto gotDependencyItem = false; if (!dependent->msg) { @@ -4490,6 +4499,7 @@ void HistoryItem::createComponentsHelper(HistoryItemCommonFields &&fields) { ? replyTo.monoforumPeerId : PeerId(); config.reply.todoItemId = replyTo.todoItemId; + config.reply.pollOption = replyTo.pollOption; const auto replyToTop = replyTo.topicRootId ? replyTo.topicRootId : LookupReplyToTop(_history, to); @@ -4960,6 +4970,28 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) { ) | ranges::views::transform([&](const MTPTodoItem &item) { return TodoListItemFromMTP(session, item); }) | ranges::to_vector; + } else if (type == mtpc_messageActionPollAppendAnswer) { + const auto &data = action.c_messageActionPollAppendAnswer(); + UpdateComponents(HistoryServicePollAppendAnswer::Bit()); + const auto append = Get(); + data.vanswer().match([&](const MTPDpollAnswer &answer) { + append->answer.option = answer.voption().v; + append->answer.text = Api::ParseTextWithEntities( + &_history->session(), + answer.vtext()); + }, [](const auto &) { + }); + } else if (type == mtpc_messageActionPollDeleteAnswer) { + const auto &data = action.c_messageActionPollDeleteAnswer(); + UpdateComponents(HistoryServicePollDeleteAnswer::Bit()); + const auto del = Get(); + data.vanswer().match([&](const MTPDpollAnswer &answer) { + del->answer.option = answer.voption().v; + del->answer.text = Api::ParseTextWithEntities( + &_history->session(), + answer.vtext()); + }, [](const auto &) { + }); } else if (type == mtpc_messageActionSuggestedPostApproval) { const auto &data = action.c_messageActionSuggestedPostApproval(); UpdateComponents(HistoryServiceSuggestDecision::Bit()); @@ -6577,6 +6609,14 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return prepareTodoAppendTasksText(); }; + auto preparePollAppendAnswer = [&](const MTPDmessageActionPollAppendAnswer &) { + return preparePollAppendAnswerText(); + }; + + auto preparePollDeleteAnswer = [&](const MTPDmessageActionPollDeleteAnswer &) { + return preparePollDeleteAnswerText(); + }; + auto prepareSuggestedPostApproval = [&](const MTPDmessageActionSuggestedPostApproval &data) { return PreparedServiceText{ { data.is_rejected() ? tr::lng_action_post_rejected(tr::now) @@ -6863,6 +6903,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { prepareConferenceCall, prepareTodoCompletions, prepareTodoAppendTasks, + preparePollAppendAnswer, prepareSuggestedPostApproval, prepareSuggestedPostSuccess, prepareSuggestedPostRefund, @@ -6875,7 +6916,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { prepareNoForwardsRequest, prepareManagedBotCreated, PrepareEmptyText, - PrepareEmptyText, + preparePollDeleteAnswer, PrepareEmptyText, PrepareErrorText)); @@ -7661,6 +7702,58 @@ PreparedServiceText HistoryItem::prepareTodoAppendTasksText() { return result; } +PreparedServiceText HistoryItem::preparePollAppendAnswerText() { + const auto append = Get(); + const auto option = append + ? append->answer.text.text + : QString(); + if (out()) { + return { + tr::lng_action_poll_added_answer_self( + tr::now, + lt_option, + { option }, + tr::marked), + }; + } + return { + .text = tr::lng_action_poll_added_answer( + tr::now, + lt_from, + fromLinkText(), + lt_option, + { option }, + tr::marked), + .links = { fromLink() }, + }; +} + +PreparedServiceText HistoryItem::preparePollDeleteAnswerText() { + const auto del = Get(); + const auto option = del + ? del->answer.text.text + : QString(); + if (out()) { + return { + tr::lng_action_poll_deleted_answer_self( + tr::now, + lt_option, + { option }, + tr::marked), + }; + } + return { + .text = tr::lng_action_poll_deleted_answer( + tr::now, + lt_from, + fromLinkText(), + lt_option, + { option }, + tr::marked), + .links = { fromLink() }, + }; +} + TextWithEntities HistoryItem::fromLinkText() const { return tr::link(st::wrap_rtl(_from->name()), 1); } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 7f396c6336..3d3d3fb8fc 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -711,6 +711,8 @@ private: TimeId scheduleDate); [[nodiscard]] PreparedServiceText prepareTodoCompletionsText(); [[nodiscard]] PreparedServiceText prepareTodoAppendTasksText(); + [[nodiscard]] PreparedServiceText preparePollAppendAnswerText(); + [[nodiscard]] PreparedServiceText preparePollDeleteAnswerText(); [[nodiscard]] PreparedServiceText composeTodoIncompleted( not_null done); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 348cd83e1d..185314bb7c 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -722,6 +722,18 @@ struct HistoryServiceTodoAppendTasks [[nodiscard]] TextWithEntities ComposeTodoTasksList( not_null append); +struct HistoryServicePollAppendAnswer +: RuntimeComponent +, HistoryServiceDependentData { + PollAnswer answer; +}; + +struct HistoryServicePollDeleteAnswer +: RuntimeComponent +, HistoryServiceDependentData { + PollAnswer answer; +}; + struct HistoryServiceSuggestDecision : RuntimeComponent , HistoryServiceDependentData { diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index b2af07bd0c..2c08e5bb70 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -823,6 +823,10 @@ TextState Service::textState(QPoint point, StateRequest request) const { result.link = done->lnk; } else if (const auto append = item->Get()) { result.link = append->lnk; + } else if (const auto pollAppend = item->Get()) { + result.link = pollAppend->lnk; + } else if (const auto pollDelete = item->Get()) { + result.link = pollDelete->lnk; } else if (const auto finish = item->Get()) { result.link = finish->lnk; } else if (media && data()->showSimilarChannels()) {