diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 2d931cffd..909bd08ce 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -1304,6 +1304,7 @@ videoChat group_call_id:int32 has_participants:Bool default_participant_id:Messa //@positions Positions of the chat in chat lists //@message_sender_id Identifier of a user or chat that is selected to send messages in the chat; may be null if the user can't change message sender //@has_protected_content True, if chat content can't be saved locally, forwarded, or copied +//@is_translatable True, if translation of all messages in the chat must be suggested to the user //@is_marked_as_unread True, if the chat is marked as unread //@is_blocked True, if the chat is blocked by the current user and private messages from the chat can't be received //@has_scheduled_messages True, if the chat has scheduled messages @@ -1326,7 +1327,7 @@ videoChat group_call_id:int32 has_participants:Bool default_participant_id:Messa //@reply_markup_message_id Identifier of the message from which reply markup needs to be used; 0 if there is no default custom reply markup in the chat //@draft_message A draft of a message in the chat; may be null //@client_data Application-specific data associated with the chat. (For example, the chat scroll position or local chat notification settings can be stored here.) Persistent if the message database is used -chat id:int53 type:ChatType title:string photo:chatPhotoInfo permissions:chatPermissions last_message:message positions:vector message_sender_id:MessageSender has_protected_content:Bool is_marked_as_unread:Bool is_blocked:Bool has_scheduled_messages:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_be_reported:Bool default_disable_notification:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 unread_reaction_count:int32 notification_settings:chatNotificationSettings available_reactions:ChatAvailableReactions message_auto_delete_time:int32 theme_name:string action_bar:ChatActionBar video_chat:videoChat pending_join_requests:chatJoinRequestsInfo reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat; +chat id:int53 type:ChatType title:string photo:chatPhotoInfo permissions:chatPermissions last_message:message positions:vector message_sender_id:MessageSender has_protected_content:Bool is_translatable:Bool is_marked_as_unread:Bool is_blocked:Bool has_scheduled_messages:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_be_reported:Bool default_disable_notification:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 unread_reaction_count:int32 notification_settings:chatNotificationSettings available_reactions:ChatAvailableReactions message_auto_delete_time:int32 theme_name:string action_bar:ChatActionBar video_chat:videoChat pending_join_requests:chatJoinRequestsInfo reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat; //@description Represents a list of chats @total_count Approximate total number of chats found @chat_ids List of chat identifiers chats total_count:int32 chat_ids:vector = Chats; @@ -5176,14 +5177,17 @@ updateChatDefaultDisableNotification chat_id:int53 default_disable_notification: //@description A chat content was allowed or restricted for saving @chat_id Chat identifier @has_protected_content New value of has_protected_content updateChatHasProtectedContent chat_id:int53 has_protected_content:Bool = Update; -//@description A chat's has_scheduled_messages field has changed @chat_id Chat identifier @has_scheduled_messages New value of has_scheduled_messages -updateChatHasScheduledMessages chat_id:int53 has_scheduled_messages:Bool = Update; +//@description Translation of chat messages was enabled or disabled @chat_id Chat identifier @is_translatable New value of is_translatable +updateChatIsTranslatable chat_id:int53 is_translatable:Bool = Update; + +//@description A chat was marked as unread or was read @chat_id Chat identifier @is_marked_as_unread New value of is_marked_as_unread +updateChatIsMarkedAsUnread chat_id:int53 is_marked_as_unread:Bool = Update; //@description A chat was blocked or unblocked @chat_id Chat identifier @is_blocked New value of is_blocked updateChatIsBlocked chat_id:int53 is_blocked:Bool = Update; -//@description A chat was marked as unread or was read @chat_id Chat identifier @is_marked_as_unread New value of is_marked_as_unread -updateChatIsMarkedAsUnread chat_id:int53 is_marked_as_unread:Bool = Update; +//@description A chat's has_scheduled_messages field has changed @chat_id Chat identifier @has_scheduled_messages New value of has_scheduled_messages +updateChatHasScheduledMessages chat_id:int53 has_scheduled_messages:Bool = Update; //@description The list of chat filters or a chat filter has changed @chat_filters The new list of chat filters @main_chat_list_position Position of the main chat list among chat filters, 0-based updateChatFilters chat_filters:vector main_chat_list_position:int32 = Update; @@ -6599,6 +6603,9 @@ setChatNotificationSettings chat_id:int53 notification_settings:chatNotification //@has_protected_content New value of has_protected_content toggleChatHasProtectedContent chat_id:int53 has_protected_content:Bool = Ok; +//@description Changes the tranlatable state of a chat; for Telegram Premium users only @chat_id Chat identifier @is_translatable New value of is_translatable +toggleChatIsTranslatable chat_id:int53 is_translatable:Bool = Ok; + //@description Changes the marked as unread state of a chat @chat_id Chat identifier @is_marked_as_unread New value of is_marked_as_unread toggleChatIsMarkedAsUnread chat_id:int53 is_marked_as_unread:Bool = Ok; diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index 99247587a..1b0ce7efd 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -11929,6 +11929,8 @@ void ContactsManager::on_get_user_full(tl_object_ptr &&u td_->messages_manager_->on_update_dialog_is_blocked(DialogId(user_id), user->blocked_); + td_->messages_manager_->on_update_dialog_is_translatable(DialogId(user_id), !user->translations_disabled_); + UserFull *user_full = add_user_full(user_id); user_full->expires_at = Time::now() + USER_FULL_EXPIRE_TIME; @@ -12271,6 +12273,8 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c td_->messages_manager_->on_update_dialog_message_ttl(DialogId(chat_id), MessageTtl(chat->ttl_period_)); + td_->messages_manager_->on_update_dialog_is_translatable(DialogId(chat_id), !chat->translations_disabled_); + ChatFull *chat_full = add_chat_full(chat_id); on_update_chat_full_invite_link(chat_full, std::move(chat->exported_invite_)); auto photo = get_photo(td_->file_manager_.get(), std::move(chat->chat_photo_), DialogId(chat_id)); @@ -12356,6 +12360,8 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c td_->messages_manager_->on_update_dialog_message_ttl(DialogId(channel_id), MessageTtl(channel->ttl_period_)); + td_->messages_manager_->on_update_dialog_is_translatable(DialogId(channel_id), !channel->translations_disabled_); + auto c = get_channel(channel_id); if (c == nullptr) { LOG(ERROR) << channel_id << " not found"; diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index cd43d34fc..ac3c893c7 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -1827,6 +1827,57 @@ class ToggleDialogUnreadMarkQuery final : public Td::ResultHandler { } }; +class ToggleDialogTranslationsQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + bool is_translatable_; + + public: + explicit ToggleDialogTranslationsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, bool is_translatable) { + dialog_id_ = dialog_id; + is_translatable_ = is_translatable; + + auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); + if (input_peer == nullptr) { + return on_error(Status::Error(400, "Can't access the chat")); + } + + int32 flags = 0; + if (!is_translatable) { + flags |= telegram_api::messages_togglePeerTranslations::DISABLED_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::messages_togglePeerTranslations(flags, false /*ignored*/, std::move(input_peer)), {{dialog_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + if (!result) { + return on_error(Status::Error(400, "Toggle dialog translations failed")); + } + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogTranslationsQuery")) { + LOG(ERROR) << "Receive error for ToggleDialogTranslationsQuery: " << status; + } + if (!G()->close_flag()) { + td_->messages_manager_->on_update_dialog_is_translatable(dialog_id_, !is_translatable_); + } + promise_.set_error(std::move(status)); + } +}; + class ToggleDialogIsBlockedQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; @@ -5519,6 +5570,7 @@ void MessagesManager::Dialog::store(StorerT &storer) const { STORE_FLAG(has_available_reactions); STORE_FLAG(has_history_generation); STORE_FLAG(need_repair_unread_reaction_count); + STORE_FLAG(is_translatable); END_STORE_FLAGS(); } @@ -5782,6 +5834,7 @@ void MessagesManager::Dialog::parse(ParserT &parser) { PARSE_FLAG(has_available_reactions); PARSE_FLAG(has_history_generation); PARSE_FLAG(need_repair_unread_reaction_count); + PARSE_FLAG(is_translatable); END_PARSE_FLAGS(); } else { need_repair_action_bar = false; @@ -20890,6 +20943,74 @@ void MessagesManager::toggle_dialog_is_marked_as_unread_on_server(DialogId dialo ->send(dialog_id, is_marked_as_unread); } +Status MessagesManager::toggle_dialog_is_translatable(DialogId dialog_id, bool is_translatable) { + Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_is_translatable"); + if (d == nullptr) { + return Status::Error(400, "Chat not found"); + } + if (!have_input_peer(dialog_id, AccessRights::Read)) { + return Status::Error(400, "Can't access the chat"); + } + if (!td_->option_manager_->get_option_boolean("is_premium")) { + return Status::Error(400, "The method is available for Telegram Premium users only"); + } + + if (is_translatable == d->is_translatable) { + return Status::OK(); + } + + set_dialog_is_translatable(d, is_translatable); + + toggle_dialog_is_translatable_on_server(dialog_id, is_translatable, 0); + return Status::OK(); +} + +class MessagesManager::ToggleDialogIsTranslatableOnServerLogEvent { + public: + DialogId dialog_id_; + bool is_translatable_; + + template + void store(StorerT &storer) const { + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_translatable_); + END_STORE_FLAGS(); + + td::store(dialog_id_, storer); + } + + template + void parse(ParserT &parser) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_translatable_); + END_PARSE_FLAGS(); + + td::parse(dialog_id_, parser); + } +}; + +uint64 MessagesManager::save_toggle_dialog_is_translatable_on_server_log_event(DialogId dialog_id, + bool is_translatable) { + ToggleDialogIsTranslatableOnServerLogEvent log_event{dialog_id, is_translatable}; + return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsTranslatableOnServer, + get_log_event_storer(log_event)); +} + +void MessagesManager::toggle_dialog_is_translatable_on_server(DialogId dialog_id, bool is_translatable, + uint64 log_event_id) { + if (log_event_id == 0 && dialog_id.get_type() == DialogType::SecretChat) { + // don't even create new binlog events + return; + } + + if (log_event_id == 0 && G()->parameters().use_message_db) { + log_event_id = save_toggle_dialog_is_translatable_on_server_log_event(dialog_id, is_translatable); + } + + td_->create_handler(get_erase_log_event_promise(log_event_id)) + ->send(dialog_id, is_translatable); +} + Status MessagesManager::toggle_message_sender_is_blocked(const td_api::object_ptr &sender, bool is_blocked) { TRY_RESULT(dialog_id, get_message_sender_dialog_id(td_, sender, true, false)); @@ -21947,18 +22068,20 @@ td_api::object_ptr MessagesManager::get_default_message_s td_api::object_ptr MessagesManager::get_chat_object(const Dialog *d) const { CHECK(d != nullptr); + bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); auto chat_source = is_dialog_sponsored(d) ? sponsored_dialog_source_.get_chat_source_object() : nullptr; auto can_delete = can_delete_dialog(d); // TODO hide/show draft message when can_send_message(dialog_id) changes auto draft_message = can_send_message(d->dialog_id).is_ok() ? get_draft_message_object(d->draft_message) : nullptr; auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object(); + auto is_translatable = d->is_translatable && is_premium; return make_tl_object( d->dialog_id.get(), get_chat_type_object(d->dialog_id), get_dialog_title(d->dialog_id), get_chat_photo_info_object(td_->file_manager_.get(), get_dialog_photo(d->dialog_id)), get_dialog_default_permissions(d->dialog_id).get_chat_permissions_object(), get_message_object(d->dialog_id, get_message(d, d->last_message_id), "get_chat_object"), get_chat_positions_object(d), get_default_message_sender_object(d), - get_dialog_has_protected_content(d->dialog_id), d->is_marked_as_unread, d->is_blocked, + get_dialog_has_protected_content(d->dialog_id), is_translatable, d->is_marked_as_unread, d->is_blocked, get_dialog_has_scheduled_messages(d), can_delete.for_self_, can_delete.for_all_users_, can_report_dialog(d->dialog_id), d->notification_settings.silent_send_message, d->server_unread_count + d->local_unread_count, d->last_read_inbox_message_id.get(), @@ -32842,6 +32965,50 @@ void MessagesManager::set_dialog_is_marked_as_unread(Dialog *d, bool is_marked_a } } +void MessagesManager::on_update_dialog_is_translatable(DialogId dialog_id, bool is_translatable) { + if (td_->auth_manager_->is_bot()) { + // just in case + return; + } + + if (!dialog_id.is_valid()) { + LOG(ERROR) << "Receive marking as unread of invalid " << dialog_id; + return; + } + + auto d = get_dialog_force(dialog_id, "on_update_dialog_is_translatable"); + if (d == nullptr) { + // nothing to do + return; + } + + if (is_translatable == d->is_translatable) { + return; + } + + set_dialog_is_translatable(d, is_translatable); +} + +void MessagesManager::set_dialog_is_translatable(Dialog *d, bool is_translatable) { + if (td_->auth_manager_->is_bot()) { + // just in case + return; + } + + CHECK(d != nullptr); + CHECK(d->is_translatable != is_translatable); + d->is_translatable = is_translatable; + on_dialog_updated(d->dialog_id, "set_dialog_is_translatable"); + + LOG(INFO) << "Set " << d->dialog_id << " is translatable to " << is_translatable; + LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_is_translatable"; + bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); + if (is_premium) { + send_closure(G()->td(), &Td::send_update, + make_tl_object(d->dialog_id.get(), is_translatable)); + } +} + void MessagesManager::on_update_dialog_is_blocked(DialogId dialog_id, bool is_blocked) { if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive pinned message in invalid " << dialog_id; diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 6f0e95650..734c10558 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -287,6 +287,8 @@ class MessagesManager final : public Actor { void on_update_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread); + void on_update_dialog_is_translatable(DialogId dialog_id, bool is_translatable); + void on_update_dialog_is_blocked(DialogId dialog_id, bool is_blocked); void on_update_dialog_last_pinned_message_id(DialogId dialog_id, MessageId last_pinned_message_id); @@ -678,6 +680,8 @@ class MessagesManager final : public Actor { Status toggle_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread) TD_WARN_UNUSED_RESULT; + Status toggle_dialog_is_translatable(DialogId dialog_id, bool is_translatable) TD_WARN_UNUSED_RESULT; + Status toggle_message_sender_is_blocked(const td_api::object_ptr &sender, bool is_blocked) TD_WARN_UNUSED_RESULT; @@ -1380,6 +1384,7 @@ class MessagesManager final : public Actor { bool is_available_reactions_inited = false; bool had_yet_unsent_message_id_overflow = false; bool need_repair_unread_reaction_count = false; + bool is_translatable = false; bool increment_view_counter = false; @@ -1784,6 +1789,7 @@ class MessagesManager final : public Actor { class SetDialogFolderIdOnServerLogEvent; class ToggleDialogIsBlockedOnServerLogEvent; class ToggleDialogIsMarkedAsUnreadOnServerLogEvent; + class ToggleDialogIsTranslatableOnServerLogEvent; class ToggleDialogIsPinnedOnServerLogEvent; class ToggleDialogReportSpamStateOnServerLogEvent; class UnpinAllDialogMessagesOnServerLogEvent; @@ -2657,6 +2663,8 @@ class MessagesManager final : public Actor { void set_dialog_is_marked_as_unread(Dialog *d, bool is_marked_as_unread); + void set_dialog_is_translatable(Dialog *d, bool is_translatable); + void set_dialog_is_blocked(Dialog *d, bool is_blocked); void set_dialog_has_bots(Dialog *d, bool has_bots); @@ -2689,6 +2697,8 @@ class MessagesManager final : public Actor { void toggle_dialog_is_marked_as_unread_on_server(DialogId dialog_id, bool is_marked_as_unread, uint64 log_event_id); + void toggle_dialog_is_translatable_on_server(DialogId dialog_id, bool is_translatable, uint64 log_event_id); + void toggle_dialog_is_blocked_on_server(DialogId dialog_id, bool is_blocked, uint64 log_event_id); void reorder_pinned_dialogs_on_server(FolderId folder_id, const vector &dialog_ids, uint64 log_event_id); @@ -3346,6 +3356,8 @@ class MessagesManager final : public Actor { static uint64 save_toggle_dialog_is_marked_as_unread_on_server_log_event(DialogId dialog_id, bool is_marked_as_unread); + static uint64 save_toggle_dialog_is_translatable_on_server_log_event(DialogId dialog_id, bool is_translatable); + static uint64 save_toggle_dialog_is_blocked_on_server_log_event(DialogId dialog_id, bool is_blocked); static uint64 save_read_message_contents_on_server_log_event(DialogId dialog_id, diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 05994b0b9..b836ce725 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -6188,6 +6188,12 @@ void Td::on_request(uint64 id, const td_api::toggleChatIsPinned &request) { DialogId(request.chat_id_), request.is_pinned_)); } +void Td::on_request(uint64 id, const td_api::toggleChatIsTranslatable &request) { + CHECK_IS_USER(); + answer_ok_query( + id, messages_manager_->toggle_dialog_is_translatable(DialogId(request.chat_id_), request.is_translatable_)); +} + void Td::on_request(uint64 id, const td_api::toggleChatIsMarkedAsUnread &request) { CHECK_IS_USER(); answer_ok_query(id, messages_manager_->toggle_dialog_is_marked_as_unread(DialogId(request.chat_id_), diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 96d503ca7..606f0889f 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -913,6 +913,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::toggleChatIsPinned &request); + void on_request(uint64 id, const td_api::toggleChatIsTranslatable &request); + void on_request(uint64 id, const td_api::toggleChatIsMarkedAsUnread &request); void on_request(uint64 id, const td_api::toggleMessageSenderIsBlocked &request); diff --git a/td/telegram/TdDb.cpp b/td/telegram/TdDb.cpp index afa1aea06..137339951 100644 --- a/td/telegram/TdDb.cpp +++ b/td/telegram/TdDb.cpp @@ -121,6 +121,7 @@ Status init_binlog(Binlog &binlog, string path, BinlogKeyValue &binlog_p case LogEvent::HandlerType::DeleteDialogMessagesByDateOnServer: case LogEvent::HandlerType::ReadAllDialogReactionsOnServer: case LogEvent::HandlerType::DeleteTopicHistoryOnServer: + case LogEvent::HandlerType::ToggleDialogIsTranslatableOnServer: events.to_messages_manager.push_back(event.clone()); break; case LogEvent::HandlerType::UpdateScopeNotificationSettingsOnServer: diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 0a51d53bb..f551573c1 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -3629,9 +3629,14 @@ class CliClient final : public Actor { send_request(td_api::make_object(as_chat_list(op), chat_id, is_pinned)); } else if (op == "tcimau") { ChatId chat_id; - bool is_marked_as_read; - get_args(args, chat_id, is_marked_as_read); - send_request(td_api::make_object(chat_id, is_marked_as_read)); + bool is_marked_as_unread; + get_args(args, chat_id, is_marked_as_unread); + send_request(td_api::make_object(chat_id, is_marked_as_unread)); + } else if (op == "tcit") { + ChatId chat_id; + bool is_translatable; + get_args(args, chat_id, is_translatable); + send_request(td_api::make_object(chat_id, is_translatable)); } else if (op == "tmsib") { string sender_id; bool is_blocked; diff --git a/td/telegram/logevent/LogEvent.h b/td/telegram/logevent/LogEvent.h index c0ae154f3..4464af9cf 100644 --- a/td/telegram/logevent/LogEvent.h +++ b/td/telegram/logevent/LogEvent.h @@ -102,6 +102,7 @@ class LogEvent { DeleteDialogMessagesByDateOnServer = 0x123, ReadAllDialogReactionsOnServer = 0x124, DeleteTopicHistoryOnServer = 0x125, + ToggleDialogIsTranslatableOnServer = 0x126, GetChannelDifference = 0x140, AddMessagePushNotification = 0x200, EditMessagePushNotification = 0x201,