diff --git a/CMakeLists.txt b/CMakeLists.txt index aabad071e..50beaa4ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) -project(TDLib VERSION 1.7.0 LANGUAGES CXX C) +project(TDLib VERSION 1.7.2 LANGUAGES CXX C) if (NOT DEFINED CMAKE_MODULE_PATH) set(CMAKE_MODULE_PATH "") @@ -201,7 +201,6 @@ endif() get_directory_property(HAS_PARENT PARENT_DIRECTORY) if (HAS_PARENT) - set(TL_TD_API_TLO ${TL_TD_API_TLO} PARENT_SCOPE) # was used in standalone binding generators set(TL_TD_JSON_AUTO ${TL_TD_JSON_AUTO_SOURCE} PARENT_SCOPE) # used in tdbot set(TD_TEST_SOURCE ${TD_TEST_SOURCE} PARENT_SCOPE) # used to build tests endif() @@ -295,6 +294,7 @@ set(TDLIB_SOURCE td/telegram/DialogDb.cpp td/telegram/DialogFilter.cpp td/telegram/DialogId.cpp + td/telegram/DialogInviteLink.cpp td/telegram/DialogLocation.cpp td/telegram/DialogParticipant.cpp td/telegram/DialogSource.cpp @@ -343,6 +343,7 @@ set(TDLIB_SOURCE td/telegram/MessagesDb.cpp td/telegram/MessageSearchFilter.cpp td/telegram/MessagesManager.cpp + td/telegram/MessageTtlSetting.cpp td/telegram/misc.cpp td/telegram/net/AuthDataShared.cpp td/telegram/net/ConnectionCreator.cpp @@ -374,6 +375,7 @@ set(TDLIB_SOURCE td/telegram/PollManager.cpp td/telegram/QueryCombiner.cpp td/telegram/ReplyMarkup.cpp + td/telegram/ReportReason.cpp td/telegram/RestrictionReason.cpp td/telegram/SecretChatActor.cpp td/telegram/SecretChatDb.cpp @@ -461,6 +463,7 @@ set(TDLIB_SOURCE td/telegram/DialogFilter.h td/telegram/DialogFilterId.h td/telegram/DialogId.h + td/telegram/DialogInviteLink.h td/telegram/DialogListId.h td/telegram/DialogLocation.h td/telegram/DialogParticipant.h @@ -523,6 +526,7 @@ set(TDLIB_SOURCE td/telegram/MessagesDb.h td/telegram/MessageSearchFilter.h td/telegram/MessagesManager.h + td/telegram/MessageTtlSetting.h td/telegram/misc.h td/telegram/net/AuthDataShared.h td/telegram/net/ConnectionCreator.h @@ -567,6 +571,7 @@ set(TDLIB_SOURCE td/telegram/PublicDialogType.h td/telegram/QueryCombiner.h td/telegram/ReplyMarkup.h + td/telegram/ReportReason.h td/telegram/RequestActor.h td/telegram/RestrictionReason.h td/telegram/ScheduledServerMessageId.h diff --git a/README.md b/README.md index 4e7effa93..26a226abc 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ target_link_libraries(YourTarget PRIVATE Td::TdStatic) Or you could install `TDLib` and then reference it in your CMakeLists.txt like this: ``` -find_package(Td 1.7.0 REQUIRED) +find_package(Td 1.7.2 REQUIRED) target_link_libraries(YourTarget PRIVATE Td::TdStatic) ``` See [example/cpp/CMakeLists.txt](https://github.com/tdlib/td/tree/master/example/cpp/CMakeLists.txt). diff --git a/SplitSource.php b/SplitSource.php index 605ee0121..2c5da8fa6 100644 --- a/SplitSource.php +++ b/SplitSource.php @@ -75,6 +75,7 @@ function split_file($file, $chunks, $undo) { $target_depth = 1 + $is_generated; $is_static = false; $in_define = false; + $in_comment = false; $current = ''; $common = ''; $functions = array(); @@ -113,6 +114,17 @@ function split_file($file, $chunks, $undo) { continue; } + if ($in_comment && strpos($line, '*/') === 0) { + $in_comment = false; + continue; + } + if (strpos($line, '/*') === 0) { + $in_comment = true; + } + if ($in_comment) { + continue; + } + if ($depth !== $target_depth) { $common .= $line; continue; @@ -139,7 +151,8 @@ function split_file($file, $chunks, $undo) { $in_define = false; } } - if (!empty(trim($current))) { + $current = trim($current); + if (!empty($current)) { fwrite(STDERR, "ERROR: $current".PHP_EOL); exit(); } @@ -285,7 +298,7 @@ function split_file($file, $chunks, $undo) { '[>](td_db[(][)]|get_td_db_impl[(])|TdDb[^A-Za-z]' => 'TdDb', 'TopDialogCategory|get_top_dialog_category' => 'TopDialogCategory', 'top_dialog_manager[_(-][^.]|TopDialogManager' => 'TopDialogManager', - 'updates_manager[_(-][^.]|UpdatesManager|get_difference[)]' => 'UpdatesManager', + 'updates_manager[_(-][^.]|UpdatesManager|get_difference[)]|updateSentMessage|dummyUpdate' => 'UpdatesManager', 'WebPageId(Hash)?' => 'WebPageId', 'web_pages_manager[_(-][^.]|WebPagesManager' => 'WebPagesManager'); diff --git a/build.html b/build.html index cfd2b192e..2cb47895f 100644 --- a/build.html +++ b/build.html @@ -715,7 +715,7 @@ function onOptionsChanged() { commands.push('git clone https://github.com/tdlib/td.git'); commands.push('cd td'); - commands.push('git checkout v1.7.0'); + // commands.push('git checkout v1.7.0'); if (use_vcpkg) { commands.push('git clone https://github.com/Microsoft/vcpkg.git'); diff --git a/example/README.md b/example/README.md index 17ecd1646..2e7273645 100644 --- a/example/README.md +++ b/example/README.md @@ -169,10 +169,10 @@ See [project.scarlet](https://github.com/aaugmentum/project.scarlet), [tdlib](ht TDLib can be used from the Rust programming language through the [JSON](https://github.com/tdlib/td#using-json) interface. -See [tdlib-rs](https://github.com/agnipau/tdlib-rs), which contains automatically generated classes for all TDLib API methods and objects. +See [rust-tdlib](https://github.com/aCLr/rust-tdlib) or [tdlib-rs](https://github.com/agnipau/tdlib-rs), which provide convenient TDLib clients with automatically generated and fully-documented classes for all TDLib API methods and objects. See [rtdlib](https://github.com/fewensa/rtdlib), [tdlib-rs](https://github.com/d653/tdlib-rs), [tdlib-futures](https://github.com/yuri91/tdlib-futures), -[tdlib-sys](https://github.com/nuxeh/tdlib-sys), [rust-tdlib](https://github.com/lattenwald/rust-tdlib), or +[tdlib-sys](https://github.com/nuxeh/tdlib-sys), or [tdjson-rs](https://github.com/mersinvald/tdjson-rs) for examples of TDLib Rust bindings. diff --git a/example/cpp/CMakeLists.txt b/example/cpp/CMakeLists.txt index 2d1e0cde0..642bbb07a 100644 --- a/example/cpp/CMakeLists.txt +++ b/example/cpp/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(TdExample VERSION 1.0 LANGUAGES CXX) -find_package(Td 1.7.0 REQUIRED) +find_package(Td 1.7.2 REQUIRED) add_executable(tdjson_example tdjson_example.cpp) target_link_libraries(tdjson_example PRIVATE Td::TdJson) diff --git a/example/uwp/extension.vsixmanifest b/example/uwp/extension.vsixmanifest index 62a2374f5..5d7989ac2 100644 --- a/example/uwp/extension.vsixmanifest +++ b/example/uwp/extension.vsixmanifest @@ -1,6 +1,6 @@ - + TDLib for Universal Windows Platform TDLib is a library for building Telegram clients https://core.telegram.org/tdlib diff --git a/td/generate/CMakeLists.txt b/td/generate/CMakeLists.txt index e9e0c7b14..c4a7312f9 100644 --- a/td/generate/CMakeLists.txt +++ b/td/generate/CMakeLists.txt @@ -6,147 +6,92 @@ endif() file(MAKE_DIRECTORY auto/td/telegram) file(MAKE_DIRECTORY auto/td/mtproto) +file(MAKE_DIRECTORY auto/tlo) set(TL_TD_AUTO_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/auto PARENT_SCOPE) set(TD_AUTO_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/auto/td) set(TL_TD_AUTO_SOURCE - ${TD_AUTO_INCLUDE_DIR}/mtproto/mtproto_api.cpp - ${TD_AUTO_INCLUDE_DIR}/mtproto/mtproto_api.h - ${TD_AUTO_INCLUDE_DIR}/mtproto/mtproto_api.hpp - ${TD_AUTO_INCLUDE_DIR}/telegram/telegram_api.cpp - ${TD_AUTO_INCLUDE_DIR}/telegram/telegram_api.h - ${TD_AUTO_INCLUDE_DIR}/telegram/telegram_api.hpp - ${TD_AUTO_INCLUDE_DIR}/telegram/secret_api.cpp - ${TD_AUTO_INCLUDE_DIR}/telegram/secret_api.h - ${TD_AUTO_INCLUDE_DIR}/telegram/secret_api.hpp - PARENT_SCOPE -) + ${TD_AUTO_INCLUDE_DIR}/mtproto/mtproto_api.cpp + ${TD_AUTO_INCLUDE_DIR}/mtproto/mtproto_api.h + ${TD_AUTO_INCLUDE_DIR}/mtproto/mtproto_api.hpp + ${TD_AUTO_INCLUDE_DIR}/telegram/telegram_api.cpp + ${TD_AUTO_INCLUDE_DIR}/telegram/telegram_api.h + ${TD_AUTO_INCLUDE_DIR}/telegram/telegram_api.hpp + ${TD_AUTO_INCLUDE_DIR}/telegram/secret_api.cpp + ${TD_AUTO_INCLUDE_DIR}/telegram/secret_api.h + ${TD_AUTO_INCLUDE_DIR}/telegram/secret_api.hpp + PARENT_SCOPE + ) set(TL_TD_API_AUTO_SOURCE - ${TD_AUTO_INCLUDE_DIR}/telegram/td_api.cpp - ${TD_AUTO_INCLUDE_DIR}/telegram/td_api.h - ${TD_AUTO_INCLUDE_DIR}/telegram/td_api.hpp - PARENT_SCOPE -) + ${TD_AUTO_INCLUDE_DIR}/telegram/td_api.cpp + ${TD_AUTO_INCLUDE_DIR}/telegram/td_api.h + ${TD_AUTO_INCLUDE_DIR}/telegram/td_api.hpp + PARENT_SCOPE + ) set(TL_TD_JSON_AUTO_SOURCE - ${TD_AUTO_INCLUDE_DIR}/telegram/td_api_json.cpp - ${TD_AUTO_INCLUDE_DIR}/telegram/td_api_json.h - PARENT_SCOPE -) - -set(TL_TD_API_TLO ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tlo) -set(TL_TD_API_TLO ${TL_TD_API_TLO} PARENT_SCOPE) + ${TD_AUTO_INCLUDE_DIR}/telegram/td_api_json.cpp + ${TD_AUTO_INCLUDE_DIR}/telegram/td_api_json.h + PARENT_SCOPE + ) set(TL_C_AUTO_SOURCE - ${TD_AUTO_INCLUDE_DIR}/telegram/td_tdc_api.cpp - ${TD_AUTO_INCLUDE_DIR}/telegram/td_tdc_api.h - ${TD_AUTO_INCLUDE_DIR}/telegram/td_tdc_api_inner.h - PARENT_SCOPE -) + ${TD_AUTO_INCLUDE_DIR}/telegram/td_tdc_api.cpp + ${TD_AUTO_INCLUDE_DIR}/telegram/td_tdc_api.h + ${TD_AUTO_INCLUDE_DIR}/telegram/td_tdc_api_inner.h + PARENT_SCOPE + ) set(TL_DOTNET_AUTO_SOURCE - ${TD_AUTO_INCLUDE_DIR}/telegram/TdDotNetApi.cpp - ${TD_AUTO_INCLUDE_DIR}/telegram/TdDotNetApi.h - PARENT_SCOPE -) + ${TD_AUTO_INCLUDE_DIR}/telegram/TdDotNetApi.cpp + ${TD_AUTO_INCLUDE_DIR}/telegram/TdDotNetApi.h + PARENT_SCOPE + ) set(TL_GENERATE_COMMON_SOURCE - generate_common.cpp + generate_common.cpp - tl_writer_cpp.cpp - tl_writer_h.cpp - tl_writer_hpp.cpp - tl_writer_jni_cpp.cpp - tl_writer_jni_h.cpp - tl_writer_td.cpp + tl_writer_cpp.cpp + tl_writer_h.cpp + tl_writer_hpp.cpp + tl_writer_jni_cpp.cpp + tl_writer_jni_h.cpp + tl_writer_td.cpp - tl_writer_cpp.h - tl_writer_h.h - tl_writer_hpp.h - tl_writer_jni_cpp.h - tl_writer_jni_h.h - tl_writer_td.h -) + tl_writer_cpp.h + tl_writer_h.h + tl_writer_hpp.h + tl_writer_jni_cpp.h + tl_writer_jni_h.h + tl_writer_td.h + ) set(TL_GENERATE_C_SOURCE - generate_c.cpp + generate_c.cpp - tl_writer_c.h -) + tl_writer_c.h + ) set(TL_GENERATE_JAVA_SOURCE - generate_java.cpp + generate_java.cpp - tl_writer_java.cpp + tl_writer_java.cpp - tl_writer_java.h -) + tl_writer_java.h + ) set(TL_GENERATE_JSON_SOURCE - generate_json.cpp + generate_json.cpp - tl_json_converter.cpp + tl_json_converter.cpp - tl_json_converter.h -) + tl_json_converter.h + ) if (NOT CMAKE_CROSSCOMPILING) - # Start of .tlo update - add_custom_target(prepare_tl_parser ALL - COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_SOURCE_DIR}/tl-parser/build) - add_custom_target(configure_tl_parser - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tl-parser/build - COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Release ../ - COMMENT "Configure tl-parser" - DEPENDS scheme/mtproto_api.tl scheme/telegram_api.tl scheme/secret_api.tl scheme/td_api.tl - ) - add_dependencies(configure_tl_parser prepare_tl_parser) - - add_custom_target(build_tl_parser - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tl-parser/build - COMMAND ${CMAKE_COMMAND} --build . - COMMENT "Build tl-parser" - DEPENDS scheme/mtproto_api.tl scheme/telegram_api.tl scheme/secret_api.tl scheme/td_api.tl - ) - add_dependencies(build_tl_parser configure_tl_parser) - set(TL_PARSER_BIN ./tl-parser/build/tl-parser) - - add_custom_target(generate_mtproto_api_tlo - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${TL_PARSER_BIN} -v -e scheme/mtproto_api.tlo scheme/mtproto_api.tl - COMMENT "Build tl-parser" - DEPENDS scheme/mtproto_api.tl - ) - add_dependencies(generate_mtproto_api_tlo build_tl_parser) - - add_custom_target(generate_secret_api_tlo - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${TL_PARSER_BIN} -v -e scheme/secret_api.tlo scheme/secret_api.tl - COMMENT "Build tl-parser" - DEPENDS scheme/secret_api.tl - ) - add_dependencies(generate_secret_api_tlo build_tl_parser) - - add_custom_target(generate_telegram_api_tlo - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${TL_PARSER_BIN} -v -e scheme/telegram_api.tlo scheme/telegram_api.tl - COMMENT "Build tl-parser" - DEPENDS scheme/telegram_api.tl - ) - add_dependencies(generate_telegram_api_tlo build_tl_parser) - - add_custom_target(generate_td_api_tlo - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${TL_PARSER_BIN} -v -e scheme/td_api.tlo scheme/td_api.tl - COMMENT "Build tl-parser" - DEPENDS scheme/td_api.tl - ) - add_dependencies(generate_td_api_tlo build_tl_parser) - # End of .tlo update - find_program(PHP_EXECUTABLE php) if ((CMAKE_SYSTEM_NAME MATCHES "FreeBSD") AND (CMAKE_SYSTEM_VERSION MATCHES "HBSD")) @@ -159,14 +104,27 @@ if (NOT CMAKE_CROSSCOMPILING) set(GENERATE_COMMON_CMD generate_common) endif() + add_subdirectory(tl-parser) + + set(TLO_AUTO_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/auto/tlo) + + add_custom_target(tl_generate_tlo + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND tl-parser -e auto/tlo/mtproto_api.tlo scheme/mtproto_api.tl + COMMAND tl-parser -e auto/tlo/secret_api.tlo scheme/secret_api.tl + COMMAND tl-parser -e auto/tlo/td_api.tlo scheme/td_api.tl + COMMAND tl-parser -e auto/tlo/telegram_api.tlo scheme/telegram_api.tl + COMMENT "Generate TLO files" + DEPENDS tl-parser ${CMAKE_CURRENT_SOURCE_DIR}/scheme/mtproto_api.tl ${CMAKE_CURRENT_SOURCE_DIR}/scheme/secret_api.tl ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl ${CMAKE_CURRENT_SOURCE_DIR}/scheme/telegram_api.tl + ) + add_executable(generate_common ${TL_GENERATE_COMMON_SOURCE}) - add_dependencies(generate_common generate_mtproto_api_tlo generate_secret_api_tlo generate_telegram_api_tlo generate_td_api_tlo) target_link_libraries(generate_common PRIVATE tdtl) add_custom_target(tl_generate_common WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${GENERATE_COMMON_CMD} - COMMENT "Generate common tl source files" - DEPENDS generate_common DoxygenTlDocumentationGenerator.php + COMMENT "Generate common TL source files" + DEPENDS generate_common tl_generate_tlo ${TLO_AUTO_INCLUDE_DIR}/mtproto_api.tlo ${TLO_AUTO_INCLUDE_DIR}/secret_api.tlo ${TLO_AUTO_INCLUDE_DIR}/td_api.tlo ${TLO_AUTO_INCLUDE_DIR}/telegram_api.tlo ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl ${CMAKE_CURRENT_SOURCE_DIR}/DoxygenTlDocumentationGenerator.php ) if (TD_ENABLE_JNI) target_compile_definitions(generate_common PRIVATE TD_ENABLE_JNI=1) @@ -178,12 +136,11 @@ if (NOT CMAKE_CROSSCOMPILING) add_executable(generate_c ${TL_GENERATE_C_SOURCE}) target_link_libraries(generate_c PRIVATE tdtl) add_custom_target(tl_generate_c - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND generate_c - COMMENT "Generate C tl source files" - DEPENDS generate_c generate_td_api_tlo - ) - add_dependencies(tl_generate_c generate_td_api_tlo) + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND generate_c + COMMENT "Generate C TL source files" + DEPENDS generate_c tl_generate_tlo ${TLO_AUTO_INCLUDE_DIR}/td_api.tlo ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl + ) add_executable(td_generate_java_api ${TL_GENERATE_JAVA_SOURCE}) target_link_libraries(td_generate_java_api PRIVATE tdtl) @@ -191,34 +148,33 @@ if (NOT CMAKE_CROSSCOMPILING) add_executable(generate_json ${TL_GENERATE_JSON_SOURCE}) target_link_libraries(generate_json PRIVATE tdtl tdutils) add_custom_target(tl_generate_json - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND generate_json - COMMENT "Generate JSON tl source files" - DEPENDS generate_json generate_td_api_tlo - ) - add_dependencies(tl_generate_json generate_td_api_tlo) + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND generate_json + COMMENT "Generate JSON TL source files" + DEPENDS generate_json tl_generate_tlo ${TLO_AUTO_INCLUDE_DIR}/td_api.tlo ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl + ) if (TD_ENABLE_JNI) install(TARGETS td_generate_java_api RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") install(FILES JavadocTlDocumentationGenerator.php TlDocumentationGenerator.php DESTINATION "${CMAKE_INSTALL_BINDIR}/td/generate") - install(FILES scheme/td_api.tlo scheme/td_api.tl DESTINATION "${CMAKE_INSTALL_BINDIR}/td/generate/scheme") + install(FILES ${TLO_AUTO_INCLUDE_DIR}/td_api.tlo scheme/td_api.tl DESTINATION "${CMAKE_INSTALL_BINDIR}/td/generate/scheme") endif() if (TD_ENABLE_DOTNET) if (PHP_EXECUTABLE) - set(GENERATE_DOTNET_API_CMD td_generate_dotnet_api ${TL_TD_API_TLO} && ${PHP_EXECUTABLE} DotnetTlDocumentationGenerator.php scheme/td_api.tl auto/td/telegram/TdDotNetApi.h) + set(GENERATE_DOTNET_API_CMD td_generate_dotnet_api ${TLO_AUTO_INCLUDE_DIR}/td_api.tlo && ${PHP_EXECUTABLE} DotnetTlDocumentationGenerator.php scheme/td_api.tl auto/td/telegram/TdDotNetApi.h) else() - set(GENERATE_DOTNET_API_CMD td_generate_dotnet_api ${TL_TD_API_TLO}) + set(GENERATE_DOTNET_API_CMD td_generate_dotnet_api ${TLO_AUTO_INCLUDE_DIR}/td_api.tlo) endif() add_executable(td_generate_dotnet_api generate_dotnet.cpp tl_writer_dotnet.h) target_link_libraries(td_generate_dotnet_api PRIVATE tdtl) add_custom_target(generate_dotnet_api - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${GENERATE_DOTNET_API_CMD} ${TL_TD_API_TLO} - COMMENT "Generate .NET API files" - DEPENDS td_generate_dotnet_api generate_td_api_tlo DotnetTlDocumentationGenerator.php - ) + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${GENERATE_DOTNET_API_CMD} + COMMENT "Generate .NET API files" + DEPENDS td_generate_dotnet_api tl_generate_tlo ${TLO_AUTO_INCLUDE_DIR}/td_api.tlo ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl ${CMAKE_CURRENT_SOURCE_DIR}/DotnetTlDocumentationGenerator.php + ) endif() add_executable(remove_documentation remove_documentation.cpp) diff --git a/td/generate/generate_c.cpp b/td/generate/generate_c.cpp index ac38a2df7..01831e72d 100644 --- a/td/generate/generate_c.cpp +++ b/td/generate/generate_c.cpp @@ -10,7 +10,7 @@ #include "td/tl/tl_generate.h" int main() { - td::tl::tl_config config_td = td::tl::read_tl_config_from_file("scheme/td_api.tlo"); + td::tl::tl_config config_td = td::tl::read_tl_config_from_file("auto/tlo/td_api.tlo"); td::tl::write_tl_to_file(config_td, "auto/td/telegram/td_tdc_api.h", td::TlWriterCCommon("TdApi", 1, "#include \"td/telegram/td_api.h\"\n")); td::tl::write_tl_to_file(config_td, "auto/td/telegram/td_tdc_api_inner.h", diff --git a/td/generate/generate_common.cpp b/td/generate/generate_common.cpp index 11f507138..8fcdc8e2c 100644 --- a/td/generate/generate_common.cpp +++ b/td/generate/generate_common.cpp @@ -22,7 +22,7 @@ static void generate_cpp(const std::string &directory, const std::string &tl_nam const std::string &bytes_type, const std::vector &ext_cpp_includes, const std::vector &ext_h_includes) { std::string path = directory + "/" + tl_name; - td::tl::tl_config config = td::tl::read_tl_config_from_file("scheme/" + tl_name + ".tlo"); + td::tl::tl_config config = td::tl::read_tl_config_from_file("auto/tlo/" + tl_name + ".tlo"); td::tl::write_tl_to_file(config, path + ".cpp", WriterCpp(tl_name, string_type, bytes_type, ext_cpp_includes)); td::tl::write_tl_to_file(config, path + ".h", WriterH(tl_name, string_type, bytes_type, ext_h_includes)); td::tl::write_tl_to_file(config, path + ".hpp", WriterHpp(tl_name, string_type, bytes_type)); diff --git a/td/generate/generate_json.cpp b/td/generate/generate_json.cpp index 993fa2933..b1e47add3 100644 --- a/td/generate/generate_json.cpp +++ b/td/generate/generate_json.cpp @@ -10,6 +10,6 @@ #include "td/tl/tl_generate.h" int main() { - td::gen_json_converter(td::tl::read_tl_config_from_file("scheme/td_api.tlo"), "td/telegram/td_api_json", + td::gen_json_converter(td::tl::read_tl_config_from_file("auto/tlo/td_api.tlo"), "td/telegram/td_api_json", td::tl::TL_writer::Server); } diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 3341e1dda..051de44c4 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -237,7 +237,7 @@ maskPosition point:MaskPoint x_shift:double y_shift:double scale:double = MaskPo closedVectorPath commands:vector = ClosedVectorPath; -//@description Describes one answer option of a poll @text Option text, 1-100 characters @voter_count Number of voters for this option, available only for closed or voted polls @vote_percentage The percentage of votes for this option, 0-100 +//@description Describes one answer option of a poll @text Option text; 1-100 characters @voter_count Number of voters for this option, available only for closed or voted polls @vote_percentage The percentage of votes for this option; 0-100 //@is_chosen True, if the option was chosen by the user @is_being_chosen True, if the option is being chosen by a pending setPollAnswer request pollOption text:string voter_count:int32 vote_percentage:int32 is_chosen:Bool is_being_chosen:Bool = PollOption; @@ -249,7 +249,7 @@ pollTypeRegular allow_multiple_answers:Bool = PollType; //@description A poll in quiz mode, which has exactly one correct answer option and can be answered only once //@correct_option_id 0-based identifier of the correct answer option; -1 for a yet unanswered poll -//@explanation Text that is shown when the user chooses an incorrect answer or taps on the lamp icon, 0-200 characters with at most 2 line feeds; empty for a yet unanswered poll +//@explanation Text that is shown when the user chooses an incorrect answer or taps on the lamp icon; 0-200 characters with at most 2 line feeds; empty for a yet unanswered poll pollTypeQuiz correct_option_id:int32 explanation:formattedText = PollType; @@ -308,7 +308,7 @@ venue location:location title:string address:string provider:string id:string ty //@param_description Game description @photo Game photo @animation Game animation; may be null game id:int64 short_name:string title:string text:formattedText description:string photo:photo animation:animation = Game; -//@description Describes a poll @id Unique poll identifier @question Poll question, 1-300 characters @options List of poll answer options +//@description Describes a poll @id Unique poll identifier @question Poll question; 1-300 characters @options List of poll answer options //@total_voter_count Total number of voters, participating in the poll @recent_voter_user_ids User identifiers of recent voters, if the poll is non-anonymous //@is_anonymous True, if the poll is anonymous @type Type of the poll //@open_period Amount of time the poll will be active after creation, in seconds @close_date Point in time (Unix timestamp) when the poll will be automatically closed @is_closed True, if the poll is closed @@ -377,7 +377,7 @@ chatPhotos total_count:int32 photos:vector = ChatPhotos; //@class InputChatPhoto @description Describes a photo to be set as a user profile or chat photo -//@description A previously used profile photo of the current user @chat_photo_id Identifier of the profile photo to reuse +//@description A previously used profile photo of the current user @chat_photo_id Identifier of the current user's profile photo to reuse inputChatPhotoPrevious chat_photo_id:int64 = InputChatPhoto; //@description A static photo in JPEG format @photo Photo to be set as profile photo. Only inputFileLocal and inputFileGenerated are allowed @@ -403,10 +403,11 @@ inputChatPhotoAnimation animation:InputFile main_frame_timestamp:double = InputC //@is_support True, if the user is Telegram support account //@restriction_reason If non-empty, it contains a human-readable description of the reason why access to this user must be restricted //@is_scam True, if many users reported this user as a scam +//@is_fake True, if many users reported this user as a fake account //@have_access If false, the user is inaccessible, and the only information known about the user is inside this class. It can't be passed to any method except GetUser //@type Type of the user //@language_code IETF language tag of the user's language; only available to bots -user id:int32 first_name:string last_name:string username:string phone_number:string status:UserStatus profile_photo:profilePhoto is_contact:Bool is_mutual_contact:Bool is_verified:Bool is_support:Bool restriction_reason:string is_scam:Bool have_access:Bool type:UserType language_code:string = User; +user id:int32 first_name:string last_name:string username:string phone_number:string status:UserStatus profile_photo:profilePhoto is_contact:Bool is_mutual_contact:Bool is_verified:Bool is_support:Bool restriction_reason:string is_scam:Bool is_fake:Bool have_access:Bool type:UserType language_code:string = User; //@description Contains full information about a user //@photo User profile photo; may be null @@ -454,17 +455,18 @@ chatMemberStatusCreator custom_title:string is_anonymous:Bool is_member:Bool = C //@description The user is a member of a chat and has some additional privileges. In basic groups, administrators can edit and delete messages sent by others, add new members, ban unprivileged members, and manage voice chats. In supergroups and channels, there are more detailed options for administrator privileges //@custom_title A custom title of the administrator; 0-16 characters without emojis; applicable to supergroups only //@can_be_edited True, if the current user can edit the administrator privileges for the called user +//@can_manage_chat True, if the administrator can get chat event log, get chat statistics, get message statistics in channels, get channel members, see anonymous administrators in supergoups and ignore slow mode. Implied by any other privilege; applicable to supergroups and channels only //@can_change_info True, if the administrator can change the chat title, photo, and other settings //@can_post_messages True, if the administrator can create channel posts; applicable to channels only //@can_edit_messages True, if the administrator can edit messages of other users and pin messages; applicable to channels only //@can_delete_messages True, if the administrator can delete messages of other users //@can_invite_users True, if the administrator can invite new users to the chat //@can_restrict_members True, if the administrator can restrict, ban, or unban chat members -//@can_pin_messages True, if the administrator can pin messages; applicable to groups only +//@can_pin_messages True, if the administrator can pin messages; applicable to basic groups and supergroups only //@can_promote_members True, if the administrator can add new administrators with a subset of their own privileges or demote administrators that were directly or indirectly promoted by them -//@can_manage_voice_chats True, if the administrator can manage voice chats; applicable to groups only +//@can_manage_voice_chats True, if the administrator can manage voice chats; applicable to basic groups and supergroups only //@is_anonymous True, if the administrator isn't shown in the chat member list and sends messages anonymously; applicable to supergroups only -chatMemberStatusAdministrator custom_title:string can_be_edited:Bool can_change_info:Bool can_post_messages:Bool can_edit_messages:Bool can_delete_messages:Bool can_invite_users:Bool can_restrict_members:Bool can_pin_messages:Bool can_promote_members:Bool can_manage_voice_chats:Bool is_anonymous:Bool = ChatMemberStatus; +chatMemberStatusAdministrator custom_title:string can_be_edited:Bool can_manage_chat:Bool can_change_info:Bool can_post_messages:Bool can_edit_messages:Bool can_delete_messages:Bool can_invite_users:Bool can_restrict_members:Bool can_pin_messages:Bool can_promote_members:Bool can_manage_voice_chats:Bool is_anonymous:Bool = ChatMemberStatus; //@description The user is a member of a chat, without any additional privileges or restrictions chatMemberStatusMember = ChatMemberStatus; @@ -479,7 +481,7 @@ chatMemberStatusRestricted is_member:Bool restricted_until_date:int32 permission chatMemberStatusLeft = ChatMemberStatus; //@description The user was banned (and hence is not a member of the chat). Implies the user can't return to the chat or view messages -//@banned_until_date Point in time (Unix timestamp) when the user will be unbanned; 0 if never. If the user is banned for more than 366 days or for less than 30 seconds from the current time, the user is considered to be banned forever +//@banned_until_date Point in time (Unix timestamp) when the user will be unbanned; 0 if never. If the user is banned for more than 366 days or for less than 30 seconds from the current time, the user is considered to be banned forever. Always 0 in basic groups chatMemberStatusBanned banned_until_date:int32 = ChatMemberStatus; @@ -545,6 +547,46 @@ supergroupMembersFilterMention query:string message_thread_id:int53 = Supergroup supergroupMembersFilterBots = SupergroupMembersFilter; +//@description Contains a chat invite link @invite_link Chat invite link @creator_user_id User identifier of an administrator created the link +//@date Point in time (Unix timestamp) when the link was created +//@edit_date Point in time (Unix timestamp) when the link was last edited; 0 if never or unknown +//@expire_date Point in time (Unix timestamp) when the link will expire; 0 if never +//@member_limit Maximum number of members, which can join the chat using the link simultaneously; 0 if not limited +//@member_count Number of chat members, which joined the chat using the link +//@is_primary True, if the link is primary. Primary invite link can't have expire date or usage limit. There is exactly one primary invite link for each administrator with can_invite_users right at a given time +//@is_revoked True, if the link was revoked +chatInviteLink invite_link:string creator_user_id:int32 date:int32 edit_date:int32 expire_date:int32 member_limit:int32 member_count:int32 is_primary:Bool is_revoked:Bool = ChatInviteLink; + +//@description Contains a list of chat invite links @total_count Approximate total count of chat invite links found @invite_links List of invite links +chatInviteLinks total_count:int32 invite_links:vector = ChatInviteLinks; + +//@description Describes a chat administrator with a number of active and revoked chat invite links +//@user_id Administrator's user identifier +//@invite_link_count Number of active invite links +//@revoked_invite_link_count Number of revoked invite links +chatInviteLinkCount user_id:int32 invite_link_count:int32 revoked_invite_link_count:int32 = ChatInviteLinkCount; + +//@description Contains a list of chat invite link counts @invite_link_counts List of invite linkcounts +chatInviteLinkCounts invite_link_counts:vector = ChatInviteLinkCounts; + +//@description Describes a chat member joined a chat by an invite link @user_id User identifier @joined_chat_date Point in time (Unix timestamp) when the user joined the chat +chatInviteLinkMember user_id:int32 joined_chat_date:int32 = ChatInviteLinkMember; + +//@description Contains a list of chat members joined a chat by an invite link @total_count Approximate total count of chat members found @members List of chat members, joined a chat by an invite link +chatInviteLinkMembers total_count:int32 members:vector = ChatInviteLinkMembers; + +//@description Contains information about a chat invite link +//@chat_id Chat identifier of the invite link; 0 if the user has no access to the chat before joining +//@accessible_for If non-zero, the amount of time for which read access to the chat will remain available, in seconds +//@type Contains information about the type of the chat +//@title Title of the chat +//@photo Chat photo; may be null +//@member_count Number of members in the chat +//@member_user_ids User identifiers of some chat members that may be known to the current user +//@is_public True, if the chat is a public supergroup or channel, i.e. it has a username or it is a location-based supergroup +chatInviteLinkInfo chat_id:int53 accessible_for:int32 type:ChatType title:string photo:chatPhotoInfo member_count:int32 member_user_ids:vector is_public:Bool = ChatInviteLinkInfo; + + //@description Represents a basic group of 0-200 users (must be upgraded to a supergroup to accommodate more than 200 users) //@id Group identifier //@member_count Number of members in the group @@ -555,11 +597,11 @@ basicGroup id:int32 member_count:int32 status:ChatMemberStatus is_active:Bool up //@description Contains full information about a basic group //@photo Chat photo; may be null -//@param_description Group description +//@param_description Group description. Updated only after the basic group is opened //@creator_user_id User identifier of the creator of the group; 0 if unknown //@members Group members -//@invite_link Invite link for this group; available only after it has been generated at least once and only for the group creator -basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int32 members:vector invite_link:string = BasicGroupFullInfo; +//@invite_link Primary invite link for this group; may be null. For chat administrators with can_invite_users right only. Updated only after the basic group is opened +basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int32 members:vector invite_link:chatInviteLink = BasicGroupFullInfo; //@description Represents a supergroup or channel with zero or more members (subscribers in the case of channels). From the point of view of the system, a channel is a special kind of a supergroup: only administrators can post and see the list of members, and posts from all administrators use the name and photo of the channel instead of individual names and profile photos. Unlike supergroups, channels can have an unlimited number of subscribers @@ -573,10 +615,12 @@ basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int32 memb //@sign_messages True, if messages sent to the channel should contain information about the sender. This field is only applicable to channels //@is_slow_mode_enabled True, if the slow mode is enabled in the supergroup //@is_channel True, if the supergroup is a channel +//@is_broadcast_group True, if the supergroup is a broadcast group, i.e. only administrators can send messages and there is no limit on number of members //@is_verified True, if the supergroup or channel is verified //@restriction_reason If non-empty, contains a human-readable description of the reason why access to this supergroup or channel must be restricted -//@is_scam True, if many users reported this supergroup as a scam -supergroup id:int32 username:string date:int32 status:ChatMemberStatus member_count:int32 has_linked_chat:Bool has_location:Bool sign_messages:Bool is_slow_mode_enabled:Bool is_channel:Bool is_verified:Bool restriction_reason:string is_scam:Bool = Supergroup; +//@is_scam True, if many users reported this supergroup or channel as a scam +//@is_fake True, if many users reported this supergroup or channel as a fake account +supergroup id:int32 username:string date:int32 status:ChatMemberStatus member_count:int32 has_linked_chat:Bool has_location:Bool sign_messages:Bool is_slow_mode_enabled:Bool is_channel:Bool is_broadcast_group:Bool is_verified:Bool restriction_reason:string is_scam:Bool is_fake:Bool = Supergroup; //@description Contains full information about a supergroup or channel //@photo Chat photo; may be null @@ -596,10 +640,10 @@ supergroup id:int32 username:string date:int32 status:ChatMemberStatus member_co //@is_all_history_available True, if new chat members will have access to old messages. In public or discussion groups and both public and private channels, old messages are always available, so this option affects only private supergroups without a linked chat. The value of this field is only available for chat administrators //@sticker_set_id Identifier of the supergroup sticker set; 0 if none //@location Location to which the supergroup is connected; may be null -//@invite_link Invite link for this chat +//@invite_link Primary invite link for this chat; may be null. For chat administrators with can_invite_users right only //@upgraded_from_basic_group_id Identifier of the basic group from which supergroup was upgraded; 0 if none //@upgraded_from_max_message_id Identifier of the last message in the basic group from which supergroup was upgraded; 0 if none -supergroupFullInfo photo:chatPhoto description:string member_count:int32 administrator_count:int32 restricted_count:int32 banned_count:int32 linked_chat_id:int53 slow_mode_delay:int32 slow_mode_delay_expires_in:double can_get_members:Bool can_set_username:Bool can_set_sticker_set:Bool can_set_location:Bool can_get_statistics:Bool is_all_history_available:Bool sticker_set_id:int64 location:chatLocation invite_link:string upgraded_from_basic_group_id:int32 upgraded_from_max_message_id:int53 = SupergroupFullInfo; +supergroupFullInfo photo:chatPhoto description:string member_count:int32 administrator_count:int32 restricted_count:int32 banned_count:int32 linked_chat_id:int53 slow_mode_delay:int32 slow_mode_delay_expires_in:double can_get_members:Bool can_set_username:Bool can_set_sticker_set:Bool can_set_location:Bool can_get_statistics:Bool is_all_history_available:Bool sticker_set_id:int64 location:chatLocation invite_link:chatInviteLink upgraded_from_basic_group_id:int32 upgraded_from_max_message_id:int53 = SupergroupFullInfo; //@class SecretChatState @description Describes the current secret chat state @@ -619,11 +663,10 @@ secretChatStateClosed = SecretChatState; //@user_id Identifier of the chat partner //@state State of the secret chat //@is_outbound True, if the chat was created by the current user; otherwise false -//@ttl Current message Time To Live setting (self-destruct timer) for the chat, in seconds //@key_hash Hash of the currently used key for comparison with the hash of the chat partner's key. This is a string of 36 little-endian bytes, which must be split into groups of 2 bits, each denoting a pixel of one of 4 colors FFFFFF, D5E6F3, 2D5775, and 2F99C9. //-The pixels must be used to make a 12x12 square image filled from left to right, top to bottom. Alternatively, the first 32 bytes of the hash can be converted to the hexadecimal format and printed as 32 2-digit hex numbers //@layer Secret chat layer; determines features supported by the chat partner's application. Video notes are supported if the layer >= 66; nested text entities and underline and strikethrough entities are supported if the layer >= 101 -secretChat id:int32 user_id:int32 state:SecretChatState is_outbound:Bool ttl:int32 key_hash:bytes layer:int32 = SecretChat; +secretChat id:int32 user_id:int32 state:SecretChatState is_outbound:Bool key_hash:bytes layer:int32 = SecretChat; //@class MessageSender @description Contains information about the sender of a message @@ -658,6 +701,9 @@ messageForwardOriginHiddenUser sender_name:string = MessageForwardOrigin; //@author_signature Original post author signature messageForwardOriginChannel chat_id:int53 message_id:int53 author_signature:string = MessageForwardOrigin; +//@description The message was imported from an exported message history @sender_name Name of the sender +messageForwardOriginMessageImport sender_name:string = MessageForwardOrigin; + //@description Contains information about a forwarded message //@origin Origin of a forwarded message @@ -858,20 +904,21 @@ chatPosition list:ChatList order:int64 is_pinned:Bool source:ChatSource = ChatPo //@has_scheduled_messages True, if the chat has scheduled messages //@can_be_deleted_only_for_self True, if the chat messages can be deleted only for the current user while other users will continue to see the messages //@can_be_deleted_for_all_users True, if the chat messages can be deleted for all users -//@can_be_reported True, if the chat can be reported to Telegram moderators through reportChat +//@can_be_reported True, if the chat can be reported to Telegram moderators through reportChat or reportChatPhoto //@default_disable_notification Default value of the disable_notification parameter, used when a message is sent to the chat //@unread_count Number of unread messages in the chat //@last_read_inbox_message_id Identifier of the last read incoming message //@last_read_outbox_message_id Identifier of the last read outgoing message //@unread_mention_count Number of unread messages with a mention/reply in the chat //@notification_settings Notification settings for this chat +//@message_ttl_setting Current message Time To Live setting (self-destruct timer) for the chat; 0 if not defined. TTL is counted from the time message or its content is viewed in secret chats and from the send date in other chats //@action_bar Describes actions which should be possible to do through a chat action bar; may be null //@voice_chat_group_call_id Group call identifier of an active voice chat; 0 if none or unknown. The voice chat can be received through the method getGroupCall //@is_voice_chat_empty True, if an active voice chat is empty //@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 Contains 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 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 notification_settings:chatNotificationSettings action_bar:ChatActionBar voice_chat_group_call_id:int32 is_voice_chat_empty:Bool 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 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 notification_settings:chatNotificationSettings message_ttl_setting:int32 action_bar:ChatActionBar voice_chat_group_call_id:int32 is_voice_chat_empty:Bool reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat; //@description Represents a list of chats @total_count Approximate total count of chats found @chat_ids List of chat identifiers chats total_count:int32 chat_ids:vector = Chats; @@ -884,21 +931,6 @@ chatNearby chat_id:int53 distance:int32 = ChatNearby; chatsNearby users_nearby:vector supergroups_nearby:vector = ChatsNearby; -//@description Contains a chat invite link @invite_link Chat invite link -chatInviteLink invite_link:string = ChatInviteLink; - -//@description Contains information about a chat invite link -//@chat_id Chat identifier of the invite link; 0 if the user has no access to the chat before joining -//@accessible_for If non-zero, the amount of time for which read access to the chat will remain available, in seconds -//@type Contains information about the type of the chat -//@title Title of the chat -//@photo Chat photo; may be null -//@member_count Number of members in the chat -//@member_user_ids User identifiers of some chat members that may be known to the current user -//@is_public True, if the chat is a public supergroup or channel, i.e. it has a username or it is a location-based supergroup -chatInviteLinkInfo chat_id:int53 accessible_for:int32 type:ChatType title:string photo:chatPhotoInfo member_count:int32 member_user_ids:vector is_public:Bool = ChatInviteLinkInfo; - - //@class PublicChatType @description Describes a type of public chats //@description The chat is public, because it has username @@ -917,6 +949,9 @@ chatActionBarReportSpam can_unarchive:Bool = ChatActionBar; //@description The chat is a location-based supergroup, which can be reported as having unrelated location using the method reportChat with the reason chatReportReasonUnrelatedLocation chatActionBarReportUnrelatedLocation = ChatActionBar; +//@description The chat is a recently created group chat, to which new members can be invited +chatActionBarInviteMembers = ChatActionBar; + //@description The chat is a private or secret chat, which can be reported using the method reportChat, or the other user can be blocked using the method blockUser, or the other user can be added to the contact list using the method addContact //@can_unarchive If true, the chat was automatically archived and can be moved back to the main chat list using addChatToList simultaneously with setting chat notification settings to default using setChatNotificationSettings //@distance If non-negative, the current user was found by the peer through searchChatsNearby and this is the distance between the users @@ -1290,12 +1325,12 @@ inputCredentialsSaved saved_credentials_id:string = InputCredentials; //@description Applies if a user enters new credentials on a payment provider website @data Contains JSON-encoded data with a credential identifier from the payment provider @allow_save True, if the credential identifier can be saved on the server side inputCredentialsNew data:string allow_save:Bool = InputCredentials; -//@description Applies if a user enters new credentials using Android Pay @data JSON-encoded data with the credential identifier -inputCredentialsAndroidPay data:string = InputCredentials; - //@description Applies if a user enters new credentials using Apple Pay @data JSON-encoded data with the credential identifier inputCredentialsApplePay data:string = InputCredentials; +//@description Applies if a user enters new credentials using Google Pay @data JSON-encoded data with the credential identifier +inputCredentialsGooglePay data:string = InputCredentials; + //@description Stripe payment provider @publishable_key Stripe API publishable key @need_country True, if the user country must be provided @need_postal_code True, if the user ZIP/postal code must be provided @need_cardholder_name True, if the cardholder name must be provided paymentsProviderStripe publishable_key:string need_country:Bool need_postal_code:Bool need_cardholder_name:Bool = PaymentsProviderStripe; @@ -1360,7 +1395,7 @@ passportElementTypePhoneNumber = PassportElementType; passportElementTypeEmailAddress = PassportElementType; -//@description Represents a date according to the Gregorian calendar @day Day of the month, 1-31 @month Month, 1-12 @year Year, 1-9999 +//@description Represents a date according to the Gregorian calendar @day Day of the month; 1-31 @month Month; 1-12 @year Year; 1-9999 date day:int32 month:int32 year:int32 = Date; //@description Contains the user's personal details @@ -1678,7 +1713,7 @@ messagePinMessage message_id:int53 = MessageContent; //@description A screenshot of a message in the chat has been taken messageScreenshotTaken = MessageContent; -//@description The TTL (Time To Live) setting messages in a secret chat has been changed @ttl New TTL +//@description The TTL (Time To Live) setting for messages in the chat has been changed @ttl New message TTL setting messageChatSetTtl ttl:int32 = MessageContent; //@description A non-standard action has happened in the chat @text Message text to be shown in the chat @@ -1851,7 +1886,7 @@ inputMessageGame bot_user_id:int32 game_short_name:string = InputMessageContent; //@payload The invoice payload @provider_token Payment provider token @provider_data JSON-encoded data about the invoice, which will be shared with the payment provider @start_parameter Unique invoice bot start_parameter for the generation of this invoice inputMessageInvoice invoice:invoice title:string description:string photo_url:string photo_size:int32 photo_width:int32 photo_height:int32 payload:bytes provider_token:string provider_data:string start_parameter:string = InputMessageContent; -//@description A message with a poll. Polls can't be sent to secret chats. Polls can be sent only to a private chat with a bot @question Poll question, 1-255 characters (up to 300 characters for bots) @options List of poll answer options, 2-10 strings 1-100 characters each +//@description A message with a poll. Polls can't be sent to secret chats. Polls can be sent only to a private chat with a bot @question Poll question; 1-255 characters (up to 300 characters for bots) @options List of poll answer options, 2-10 strings 1-100 characters each //@is_anonymous True, if the poll voters are anonymous. Non-anonymous polls can't be sent or forwarded to channels @type Type of the poll //@open_period Amount of time the poll will be active after creation, in seconds; for bots only //@close_date Point in time (Unix timestamp) when the poll will be automatically closed; for bots only @@ -2080,9 +2115,9 @@ groupCallRecentSpeaker user_id:int32 is_speaking:Bool = GroupCallRecentSpeaker; //@loaded_all_participants True, if all group call participants are loaded //@recent_speakers Recently speaking users in the group call //@mute_new_participants True, if only group call administrators can unmute new participants -//@allowed_change_mute_new_participants True, if group call administrators can enable or disable mute_new_participants setting +//@can_change_mute_new_participants True, if the current user can enable or disable mute_new_participants setting //@duration Call duration; for ended calls only -groupCall id:int32 is_active:Bool is_joined:Bool need_rejoin:Bool can_unmute_self:Bool can_be_managed:Bool participant_count:int32 loaded_all_participants:Bool recent_speakers:vector mute_new_participants:Bool allowed_change_mute_new_participants:Bool duration:int32 = GroupCall; +groupCall id:int32 is_active:Bool is_joined:Bool need_rejoin:Bool can_unmute_self:Bool can_be_managed:Bool participant_count:int32 loaded_all_participants:Bool recent_speakers:vector mute_new_participants:Bool can_change_mute_new_participants:Bool duration:int32 = GroupCall; //@description Describes a payload fingerprint for interaction with tgcalls @hash Value of the field hash @setup Value of the field setup @fingerprint Value of the field fingerprint groupCallPayloadFingerprint hash:string setup:string fingerprint:string = GroupCallPayloadFingerprint; @@ -2102,12 +2137,16 @@ groupCallJoinResponse payload:groupCallPayload candidates:vector = ChatEvents; //@member_restrictions True, if member restricted/unrestricted/banned/unbanned events should be returned //@info_changes True, if changes in chat information should be returned //@setting_changes True, if changes in chat settings should be returned +//@invite_link_changes True, if changes to invite links should be returned //@voice_chat_changes True, if voice chat actions should be returned -chatEventLogFilters message_edits:Bool message_deletions:Bool message_pins:Bool member_joins:Bool member_leaves:Bool member_invites:Bool member_promotions:Bool member_restrictions:Bool info_changes:Bool setting_changes:Bool voice_chat_changes:Bool = ChatEventLogFilters; +chatEventLogFilters message_edits:Bool message_deletions:Bool message_pins:Bool member_joins:Bool member_leaves:Bool member_invites:Bool member_promotions:Bool member_restrictions:Bool info_changes:Bool setting_changes:Bool invite_link_changes:Bool voice_chat_changes:Bool = ChatEventLogFilters; //@class LanguagePackStringValue @description Represents the value of a string in a language pack @@ -2520,7 +2578,7 @@ backgroundTypeWallpaper is_blurred:Bool is_moving:Bool = BackgroundType; //@description A PNG or TGV (gzipped subset of SVG with MIME type "application/x-tgwallpattern") pattern to be combined with the background fill chosen by the user //@fill Description of the background fill -//@intensity Intensity of the pattern when it is shown above the filled background, 0-100 +//@intensity Intensity of the pattern when it is shown above the filled background; 0-100 //@is_moving True, if the background needs to be slightly moved when device is tilted backgroundTypePattern fill:BackgroundFill intensity:int32 is_moving:Bool = BackgroundType; @@ -2588,6 +2646,18 @@ checkChatUsernameResultPublicChatsTooMuch = CheckChatUsernameResult; checkChatUsernameResultPublicGroupsUnavailable = CheckChatUsernameResult; +//@class MessageFileType @description Contains information about a file with messages exported from another app + +//@description The messages was exported from a private chat @name Name of the other party; may be empty if unrecognized +messageFileTypePrivate name:string = MessageFileType; + +//@description The messages was exported from a group chat @title Title of the group chat; may be empty if unrecognized +messageFileTypeGroup title:string = MessageFileType; + +//@description The messages was exported from a chat of unknown type +messageFileTypeUnknown = MessageFileType; + + //@class PushMessageContent @description Contains content of a push message notification //@description A general message with hidden content @is_pinned True, if the message is a pinned message with the specified content @@ -2867,8 +2937,11 @@ chatReportReasonCopyright = ChatReportReason; //@description The location-based chat is unrelated to its stated location chatReportReasonUnrelatedLocation = ChatReportReason; -//@description A custom reason provided by the user @text Report text -chatReportReasonCustom text:string = ChatReportReason; +//@description The chat represents a fake account +chatReportReasonFake = ChatReportReason; + +//@description A custom reason provided by the user +chatReportReasonCustom = ChatReportReason; //@description Contains an HTTPS link to a message in a supergroup or channel @link Message link @is_public True, if the link will work for non-members of the chat @@ -3081,6 +3154,12 @@ suggestedActionEnableArchiveAndMuteNewChats = SuggestedAction; //@description Suggests the user to check authorization phone number and change the phone number if it is inaccessible suggestedActionCheckPhoneNumber = SuggestedAction; +//@description Suggests the user to see a hint about meaning of one and two ticks on sent message +suggestedActionSeeTicksHint = SuggestedAction; + +//@description Suggests the user to convert specified supergroup to a broadcast group @supergroup_id Supergroup identifier +suggestedActionConvertToBroadcastGroup supergroup_id:int32 = SuggestedAction; + //@description Contains a counter @count Count count count:int32 = Count; @@ -3330,6 +3409,9 @@ updateChatNotificationSettings chat_id:int53 notification_settings:chatNotificat //@description Notification settings for some type of chats were updated @scope Types of chats for which notification settings were updated @notification_settings The new notification settings updateScopeNotificationSettings scope:NotificationSettingsScope notification_settings:scopeNotificationSettings = Update; +//@description The message Time To Live setting for a chat was changed @chat_id Chat identifier @message_ttl_setting New value of message_ttl_setting +updateChatMessageTtlSetting chat_id:int53 message_ttl_setting:int32 = Update; + //@description The chat action bar was changed @chat_id Chat identifier @action_bar The new value of the action bar; may be null updateChatActionBar chat_id:int53 action_bar:ChatActionBar = Update; @@ -3525,6 +3607,11 @@ updatePoll poll:poll = Update; //@description A user changed the answer to a poll; for bots only @poll_id Unique poll identifier @user_id The user, who changed the answer to the poll @option_ids 0-based identifiers of answer options, chosen by the user updatePollAnswer poll_id:int64 user_id:int32 option_ids:vector = Update; +//@description User rights changed in a chat; for bots only @chat_id Chat identifier @actor_user_id Identifier of the user, changing the rights +//@date Point in time (Unix timestamp) when the user rights was changed @invite_link If user has joined the chat using an invite link, the invite link; may be null +//@old_chat_member Previous chat member @new_chat_member New chat member +updateChatMember chat_id:int53 actor_user_id:int32 date:int32 invite_link:chatInviteLink old_chat_member:chatMember new_chat_member:chatMember = Update; + //@description Contains a list of updates @updates List of updates updates updates:vector = Updates; @@ -3802,6 +3889,9 @@ getMessageThreadHistory chat_id:int53 message_id:int53 from_message_id:int53 off //@chat_id Chat identifier @remove_from_chat_list Pass true if the chat should be removed from the chat list @revoke Pass true to try to delete chat history for all users deleteChatHistory chat_id:int53 remove_from_chat_list:Bool revoke:Bool = Ok; +//@description Deletes a chat along with all messages in the corresponding chat for all chat members; requires owner privileges. For group chats this will release the username and remove all members. Chats with more than 1000 members can't be deleted using this method @chat_id Chat identifier +deleteChat chat_id:int53 = Ok; + //@description Searches for messages with given words in the chat. Returns the results in reverse chronological order, i.e. in order of decreasing message_id. Cannot be used in secret chats with a non-empty query //-(searchSecretMessages should be used instead), or without an enabled message database. For optimal performance the number of returned messages is chosen by the library //@chat_id Identifier of the chat in which to search messages @@ -3840,6 +3930,9 @@ searchSecretMessages chat_id:int53 query:string offset:string limit:int32 filter //@limit The maximum number of messages to be returned; up to 100. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached @only_missed If true, returns only messages with missed calls searchCallMessages from_message_id:int53 limit:int32 only_missed:Bool = Messages; +//@description Deletes all call messages @revoke Pass true to delete the messages for all users +deleteAllCallMessages revoke:Bool = Ok; + //@description Returns information about the recent locations of chat members that were sent to the chat. Returns up to 1 location message per user @chat_id Chat identifier @limit The maximum number of messages to be returned searchChatRecentLocationMessages chat_id:int53 limit:int32 = Messages; @@ -3930,9 +4023,6 @@ forwardMessages chat_id:int53 from_chat_id:int53 message_ids:vector optio //@chat_id Identifier of the chat to send messages @message_ids Identifiers of the messages to resend. Message identifiers must be in a strictly increasing order resendMessages chat_id:int53 message_ids:vector = Messages; -//@description Changes the current TTL setting (sets a new self-destruct timer) in a secret chat and sends the corresponding message @chat_id Chat identifier @ttl New TTL value, in seconds -sendChatSetTtlMessage chat_id:int53 ttl:int32 = Message; - //@description Sends a notification about a screenshot taken in a chat. Supported only in private and secret chats @chat_id Chat identifier sendChatScreenshotTakenNotification chat_id:int53 = Ok; @@ -4125,6 +4215,9 @@ viewMessages chat_id:int53 message_thread_id:int53 message_ids:vector for //@description Informs TDLib that the message content has been opened (e.g., the user has opened a photo, video, document, location or venue, or has listened to an audio file or voice note message). An updateMessageContentOpened update will be generated if something has changed @chat_id Chat identifier of the message @message_id Identifier of the message with the opened content openMessageContent chat_id:int53 message_id:int53 = Ok; +//@description Returns an HTTP URL to open when user clicks on a given HTTP link. This method can be used to automatically login user on a Telegram site @link The HTTP link +getExternalLink link:string = HttpUrl; + //@description Marks all mentions in a chat as read @chat_id Chat identifier readAllChatMentions chat_id:int53 = Ok; @@ -4145,8 +4238,13 @@ createSecretChat secret_chat_id:int32 = Chat; //@description Creates a new basic group and sends a corresponding messageBasicGroupChatCreate. Returns the newly created chat @user_ids Identifiers of users to be added to the basic group @title Title of the new basic group; 1-128 characters createNewBasicGroupChat user_ids:vector title:string = Chat; -//@description Creates a new supergroup or channel and sends a corresponding messageSupergroupChatCreate. Returns the newly created chat @title Title of the new chat; 1-128 characters @is_channel True, if a channel chat should be created @param_description Chat description; 0-255 characters @location Chat location if a location-based supergroup is being created -createNewSupergroupChat title:string is_channel:Bool description:string location:chatLocation = Chat; +//@description Creates a new supergroup or channel and sends a corresponding messageSupergroupChatCreate. Returns the newly created chat +//@title Title of the new chat; 1-128 characters +//@is_channel True, if a channel chat needs to be created +//@param_description Chat description; 0-255 characters +//@location Chat location if a location-based supergroup is being created +//@for_import True, if the supergroup is created for importing messages using importMessage +createNewSupergroupChat title:string is_channel:Bool description:string location:chatLocation for_import:Bool = Chat; //@description Creates a new secret chat. Returns the newly created chat @user_id Identifier of the target user createNewSecretChat user_id:int32 = Chat; @@ -4184,14 +4282,19 @@ getRecommendedChatFilters = RecommendedChatFilters; getChatFilterDefaultIconName filter:chatFilter = Text; -//@description Changes the chat title. Supported only for basic groups, supergroups and channels. Requires can_change_info rights +//@description Changes the chat title. Supported only for basic groups, supergroups and channels. Requires can_change_info administrator right //@chat_id Chat identifier @title New title of the chat; 1-128 characters setChatTitle chat_id:int53 title:string = Ok; -//@description Changes the photo of a chat. Supported only for basic groups, supergroups and channels. Requires can_change_info rights +//@description Changes the photo of a chat. Supported only for basic groups, supergroups and channels. Requires can_change_info administrator right //@chat_id Chat identifier @photo New chat photo. Pass null to delete the chat photo setChatPhoto chat_id:int53 photo:InputChatPhoto = Ok; +//@description Changes the message TTL setting (sets a new self-destruct timer) in a chat. Requires can_delete_messages administrator right in basic groups, supergroups and channels +//-Message TTL setting of a chat with the current user (Saved Messages) and the chat 777000 (Telegram) can't be changed +//@chat_id Chat identifier @ttl New TTL value, in seconds; must be one of 0, 86400, 604800 unless chat is secret +setChatMessageTtlSetting chat_id:int53 ttl:int32 = Ok; + //@description Changes the chat members permissions. Supported only for basic groups and supergroups. Requires can_restrict_members administrator right //@chat_id Chat identifier @permissions New non-administrator members permissions in the chat setChatPermissions chat_id:int53 permissions:chatPermissions = Ok; @@ -4212,10 +4315,10 @@ toggleChatDefaultDisableNotification chat_id:int53 default_disable_notification: //@description Changes application-specific data associated with a chat @chat_id Chat identifier @client_data New value of client_data setChatClientData chat_id:int53 client_data:string = Ok; -//@description Changes information about a chat. Available for basic groups, supergroups, and channels. Requires can_change_info rights @chat_id Identifier of the chat @param_description New chat description; 0-255 characters +//@description Changes information about a chat. Available for basic groups, supergroups, and channels. Requires can_change_info administrator right @chat_id Identifier of the chat @param_description New chat description; 0-255 characters setChatDescription chat_id:int53 description:string = Ok; -//@description Changes the discussion group of a channel chat; requires can_change_info rights in the channel if it is specified @chat_id Identifier of the channel chat. Pass 0 to remove a link from the supergroup passed in the second argument to a linked channel chat (requires can_pin_messages rights in the supergroup) @discussion_chat_id Identifier of a new channel's discussion group. Use 0 to remove the discussion group. +//@description Changes the discussion group of a channel chat; requires can_change_info administrator right in the channel if it is specified @chat_id Identifier of the channel chat. Pass 0 to remove a link from the supergroup passed in the second argument to a linked channel chat (requires can_pin_messages rights in the supergroup) @discussion_chat_id Identifier of a new channel's discussion group. Use 0 to remove the discussion group. //-Use the method getSuitableDiscussionChats to find all suitable groups. Basic group chats must be first upgraded to supergroup chats. If new chat members don't have access to old messages in the supergroup, then toggleSupergroupIsAllHistoryAvailable must be used first to change that setChatDiscussionGroup chat_id:int53 discussion_chat_id:int53 = Ok; @@ -4245,18 +4348,25 @@ joinChat chat_id:int53 = Ok; //@description Removes the current user from chat members. Private and secret chats can't be left using this method @chat_id Chat identifier leaveChat chat_id:int53 = Ok; -//@description Adds a new member to a chat. Members can't be added to private or secret chats. Members will not be added until the chat state has been synchronized with the server +//@description Adds a new member to a chat. Members can't be added to private or secret chats //@chat_id Chat identifier @user_id Identifier of the user @forward_limit The number of earlier messages from the chat to be forwarded to the new member; up to 100. Ignored for supergroups and channels addChatMember chat_id:int53 user_id:int32 forward_limit:int32 = Ok; -//@description Adds multiple new members to a chat. Currently this method is only available for supergroups and channels. This method can't be used to join a chat. Members can't be added to a channel if it has more than 200 members. Members will not be added until the chat state has been synchronized with the server +//@description Adds multiple new members to a chat. Currently this method is only available for supergroups and channels. This method can't be used to join a chat. Members can't be added to a channel if it has more than 200 members //@chat_id Chat identifier @user_ids Identifiers of the users to be added to the chat. The maximum number of added users is 20 for supergroups and 100 for channels addChatMembers chat_id:int53 user_ids:vector = Ok; -//@description Changes the status of a chat member, needs appropriate privileges. This function is currently not suitable for adding new members to the chat and transferring chat ownership; instead, use addChatMember or transferChatOwnership. The chat member status will not be changed until it has been synchronized with the server +//@description Changes the status of a chat member, needs appropriate privileges. This function is currently not suitable for adding new members to the chat and transferring chat ownership; instead, use addChatMember or transferChatOwnership //@chat_id Chat identifier @user_id User identifier @status The new status of the member in the chat setChatMemberStatus chat_id:int53 user_id:int32 status:ChatMemberStatus = Ok; +//@description Bans a member in a chat. Members can't be banned in private or secret chats. In supergroups and channels, the user will not be able to return to the group on their own using invite links, etc., unless unbanned first +//@chat_id Chat identifier +//@user_id Identifier of the user +//@banned_until_date Point in time (Unix timestamp) when the user will be unbanned; 0 if never. If the user is banned for more than 366 days or for less than 30 seconds from the current time, the user is considered to be banned forever. Ignored in basic groups +//@revoke_messages Pass true to delete all messages in the chat for the user. Always true for supergroups and channels +banChatMember chat_id:int53 user_id:int32 banned_until_date:int32 revoke_messages:Bool = Ok; + //@description Checks whether the current session can be used to transfer a chat ownership to another user canTransferOwnership = CanTransferOwnershipResult; @@ -4346,14 +4456,76 @@ readFilePart file_id:int32 offset:int32 count:int32 = FilePart; deleteFile file_id:int32 = Ok; -//@description Generates a new invite link for a chat; the previously generated link is revoked. Available for basic groups, supergroups, and channels. Requires administrator privileges and can_invite_users right @chat_id Chat identifier -generateChatInviteLink chat_id:int53 = ChatInviteLink; +//@description Returns information about a file with messages exported from another app @message_file_head Beginning of the message file; up to 100 first lines +getMessageFileType message_file_head:string = MessageFileType; -//@description Checks the validity of an invite link for a chat and returns information about the corresponding chat @invite_link Invite link to be checked; should begin with "https://t.me/joinchat/", "https://telegram.me/joinchat/", or "https://telegram.dog/joinchat/" +//@description Returns a confirmation text to be shown to the user before starting message import +//@chat_id Identifier of a chat to which the messages will be imported. It must be an identifier of a private chat with a mutual contact or an identifier of a supergroup chat with can_change_info administrator right +getMessageImportConfirmationText chat_id:int53 = Text; + +//@description Imports messages exported from another app +//@chat_id Identifier of a chat to which the messages will be imported. It must be an identifier of a private chat with a mutual contact or an identifier of a supergroup chat with can_change_info administrator right +//@message_file File with messages to import. Only inputFileLocal and inputFileGenerated are supported. The file must not be previously uploaded +//@attached_files Files used in the imported messages. Only inputFileLocal and inputFileGenerated are supported. The files must not be previously uploaded +importMessages chat_id:int53 message_file:InputFile attached_files:vector = Ok; + + +//@description Replaces current primary invite link for a chat with a new primary invite link. Available for basic groups, supergroups, and channels. Requires administrator privileges and can_invite_users right @chat_id Chat identifier +replacePrimaryChatInviteLink chat_id:int53 = ChatInviteLink; + +//@description Creates a new invite link for a chat. Available for basic groups, supergroups, and channels. Requires administrator privileges and can_invite_users right in the chat +//@chat_id Chat identifier +//@expire_date Point in time (Unix timestamp) when the link will expire; pass 0 if never +//@member_limit Maximum number of chat members that can join the chat by the link simultaneously; 0-99999; pass 0 if not limited +createChatInviteLink chat_id:int53 expire_date:int32 member_limit:int32 = ChatInviteLink; + +//@description Edits a non-primary invite link for a chat. Available for basic groups, supergroups, and channels. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links +//@chat_id Chat identifier +//@invite_link Invite link to be edited +//@expire_date Point in time (Unix timestamp) when the link will expire; pass 0 if never +//@member_limit Maximum number of chat members that can join the chat by the link simultaneously; 0-99999; pass 0 if not limited +editChatInviteLink chat_id:int53 invite_link:string expire_date:int32 member_limit:int32 = ChatInviteLink; + +//@description Returns information about an invite link. Requires administrator privileges and can_invite_users right in the chat to get own links and owner privileges to get other links +//@chat_id Chat identifier +//@invite_link Invite link to get +getChatInviteLink chat_id:int53 invite_link:string = ChatInviteLink; + +//@description Returns list of chat administrators with number of their invite links. Requires owner privileges in the chat @chat_id Chat identifier +getChatInviteLinkCounts chat_id:int53 = ChatInviteLinkCounts; + +//@description Returns invite links for a chat created by specified administrator. Requires administrator privileges and can_invite_users right in the chat to get own links and owner privileges to get other links +//@chat_id Chat identifier +//@creator_user_id User identifier of a chat administrator. Must be an identifier of the current user for non-owner +//@is_revoked Pass true if revoked links needs to be returned instead of active or expired +//@offset_date Creation date of an invite link starting after which to return invite links; use 0 to get results from the beginning +//@offset_invite_link Invite link starting after which to return invite links; use empty string to get results from the beginning +//@limit Maximum number of invite links to return +getChatInviteLinks chat_id:int53 creator_user_id:int32 is_revoked:Bool offset_date:int32 offset_invite_link:string limit:int32 = ChatInviteLinks; + +//@description Returns chat members joined a chat by an invite link. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links @chat_id Chat identifier @invite_link Invite link for which to return chat members +//@offset_member A chat member from which to return next chat members; use null to get results from the beginning @limit Maximum number of chat members to return +getChatInviteLinkMembers chat_id:int53 invite_link:string offset_member:chatInviteLinkMember limit:int32 = ChatInviteLinkMembers; + +//@description Revokes invite link for a chat. Available for basic groups, supergroups, and channels. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links. +//-If a primary link is revoked, then additionally to the revoked link returns new primary link +//@chat_id Chat identifier +//@invite_link Invite link to be revoked +revokeChatInviteLink chat_id:int53 invite_link:string = ChatInviteLinks; + +//@description Deletes revoked chat invite links. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links @chat_id Chat identifier @invite_link Invite link to revoke +deleteRevokedChatInviteLink chat_id:int53 invite_link:string = Ok; + +//@description Deletes all revoked chat invite links created by a given chat administrator. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links +//@chat_id Chat identifier +//@creator_user_id User identifier of a chat administrator, which links will be deleted. Must be an identifier of the current user for non-owner +deleteAllRevokedChatInviteLinks chat_id:int53 creator_user_id:int32 = Ok; + +//@description Checks the validity of an invite link for a chat and returns information about the corresponding chat @invite_link Invite link to be checked; must have URL "t.me", "telegram.me", or "telegram.dog" and query beginning with "/joinchat/" or "/+" checkChatInviteLink invite_link:string = ChatInviteLinkInfo; -//@description Uses an invite link to add the current user to the chat if possible. The new member will not be added until the chat state has been synchronized with the server -//@invite_link Invite link to import; should begin with "https://t.me/joinchat/", "https://telegram.me/joinchat/", or "https://telegram.dog/joinchat/" +//@description Uses an invite link to add the current user to the chat if possible +//@invite_link Invite link to import; must have URL "t.me", "telegram.me", or "telegram.dog" and query beginning with "/joinchat/" or "/+" joinChatByInviteLink invite_link:string = Chat; @@ -4385,7 +4557,7 @@ getGroupCall group_call_id:int32 = GroupCall; //@description Joins a group call @group_call_id Group call identifier @payload Group join payload, received from tgcalls. Use null to cancel previous joinGroupCall request @source Caller synchronization source identifier; received from tgcalls @is_muted True, if the user's microphone is muted joinGroupCall group_call_id:int32 payload:groupCallPayload source:int32 is_muted:Bool = GroupCallJoinResponse; -//@description Toggles whether new participants of a group call can be unmuted only by administrators of the group call. Requires can_manage_voice_chats rights in the corresponding chat and allowed_change_mute_mew_participants group call flag +//@description Toggles whether new participants of a group call can be unmuted only by administrators of the group call. Requires groupCall.can_change_mute_new_participants group call flag //@group_call_id Group call identifier @mute_new_participants New value of the mute_new_participants setting toggleGroupCallMuteNewParticipants group_call_id:int32 mute_new_participants:Bool = Ok; @@ -4401,6 +4573,10 @@ setGroupCallParticipantIsSpeaking group_call_id:int32 source:int32 is_speaking:B //@group_call_id Group call identifier @user_id User identifier @is_muted Pass true if the user must be muted and false otherwise toggleGroupCallParticipantIsMuted group_call_id:int32 user_id:int32 is_muted:Bool = Ok; +//@description Changes a group call participant's volume level. If the current user can manage the group call, then the participant's volume level will be changed for all users with default volume level +//@group_call_id Group call identifier @user_id User identifier @volume_level New participant's volume level; 1-20000 in hundreds of percents +setGroupCallParticipantVolumeLevel group_call_id:int32 user_id:int32 volume_level:int32 = Ok; + //@description Loads more group call participants. The loaded participants will be received through updates. Use the field groupCall.loaded_all_participants to check whether all participants has already been loaded //@group_call_id Group call identifier. The group call must be previously received through getGroupCall and must be joined or being joined //@limit Maximum number of participants to load @@ -4409,7 +4585,7 @@ loadGroupCallParticipants group_call_id:int32 limit:int32 = Ok; //@description Leaves a group call @group_call_id Group call identifier leaveGroupCall group_call_id:int32 = Ok; -//@description Discards a group call. Requires can_manage_voice_chats rights in the corresponding chat @group_call_id Group call identifier +//@description Discards a group call. Requires groupCall.can_be_managed @group_call_id Group call identifier discardGroupCall group_call_id:int32 = Ok; @@ -4620,15 +4796,18 @@ disconnectAllWebsites = Ok; //@description Changes the username of a supergroup or channel, requires owner privileges in the supergroup or channel @supergroup_id Identifier of the supergroup or channel @username New value of the username. Use an empty string to remove the username setSupergroupUsername supergroup_id:int32 username:string = Ok; -//@description Changes the sticker set of a supergroup; requires can_change_info rights @supergroup_id Identifier of the supergroup @sticker_set_id New value of the supergroup sticker set identifier. Use 0 to remove the supergroup sticker set +//@description Changes the sticker set of a supergroup; requires can_change_info administrator right @supergroup_id Identifier of the supergroup @sticker_set_id New value of the supergroup sticker set identifier. Use 0 to remove the supergroup sticker set setSupergroupStickerSet supergroup_id:int32 sticker_set_id:int64 = Ok; -//@description Toggles sender signatures messages sent in a channel; requires can_change_info rights @supergroup_id Identifier of the channel @sign_messages New value of sign_messages +//@description Toggles sender signatures messages sent in a channel; requires can_change_info administrator right @supergroup_id Identifier of the channel @sign_messages New value of sign_messages toggleSupergroupSignMessages supergroup_id:int32 sign_messages:Bool = Ok; -//@description Toggles whether the message history of a supergroup is available to new members; requires can_change_info rights @supergroup_id The identifier of the supergroup @is_all_history_available The new value of is_all_history_available +//@description Toggles whether the message history of a supergroup is available to new members; requires can_change_info administrator right @supergroup_id The identifier of the supergroup @is_all_history_available The new value of is_all_history_available toggleSupergroupIsAllHistoryAvailable supergroup_id:int32 is_all_history_available:Bool = Ok; +//@description Upgrades supergroup to a broadcast group; requires owner privileges in the supergroup @supergroup_id Identifier of the supergroup +toggleSupergroupIsBroadcastGroup supergroup_id:int32 = Ok; + //@description Reports some messages from a user in a supergroup as spam; requires administrator rights in the supergroup @supergroup_id Supergroup identifier @user_id User identifier @message_ids Identifiers of messages sent in the supergroup by the user. This list must be non-empty reportSupergroupSpam supergroup_id:int32 user_id:int32 message_ids:vector = Ok; @@ -4636,9 +4815,6 @@ reportSupergroupSpam supergroup_id:int32 user_id:int32 message_ids:vector //@filter The type of users to return. By default, supergroupMembersFilterRecent @offset Number of users to skip @limit The maximum number of users be returned; up to 200 getSupergroupMembers supergroup_id:int32 filter:SupergroupMembersFilter offset:int32 limit:int32 = ChatMembers; -//@description Deletes a supergroup or channel along with all messages in the corresponding chat. This will release the supergroup or channel username and remove all members; requires owner privileges in the supergroup or channel. Chats with more than 1000 members can't be deleted using this method @supergroup_id Identifier of the supergroup or channel -deleteSupergroup supergroup_id:int32 = Ok; - //@description Closes a secret chat, effectively transferring its state to secretChatStateClosed @secret_chat_id Secret chat identifier closeSecretChat secret_chat_id:int32 = Ok; @@ -4771,9 +4947,13 @@ deleteAccount reason:string = Ok; //@description Removes a chat action bar without any other action @chat_id Chat identifier removeChatActionBar chat_id:int53 = Ok; -//@description Reports a chat to the Telegram moderators. A chat can be reported only from the chat action bar, or if this is a private chats with a bot, a private chat with a user sharing their location, a supergroup, or a channel, since other chats can't be checked by moderators @chat_id Chat identifier @reason The reason for reporting the chat @message_ids Identifiers of reported messages, if any -reportChat chat_id:int53 reason:ChatReportReason message_ids:vector = Ok; +//@description Reports a chat to the Telegram moderators. A chat can be reported only from the chat action bar, or if this is a private chat with a bot, a private chat with a user sharing their location, a supergroup, or a channel, since other chats can't be checked by moderators +//@chat_id Chat identifier @message_ids Identifiers of reported messages, if any @reason The reason for reporting the chat @text Additional report details; 0-1024 characters +reportChat chat_id:int53 message_ids:vector reason:ChatReportReason text:string = Ok; +//@description Reports a chat photo to the Telegram moderators. A chat photo can be reported only if this is a private chat with a bot, a private chat with a user sharing their location, a supergroup, or a channel, since other chats can't be checked by moderators +//@chat_id Chat identifier @file_id Identifier of the photo to report. Only full photos from chatPhoto can be reported @reason The reason for reporting the chat photo @text Additional report details; 0-1024 characters +reportChatPhoto chat_id:int53 file_id:int32 reason:ChatReportReason text:string = Ok; //@description Returns an HTTP URL with the chat statistics. Currently this method of getting the statistics are disabled and can be deleted in the future @chat_id Chat identifier @parameters Parameters from "tg://statsrefresh?params=******" link @is_dark Pass true if a URL with the dark theme must be returned getChatStatisticsUrl chat_id:int53 parameters:string is_dark:Bool = HttpUrl; @@ -4999,7 +5179,7 @@ removeProxy proxy_id:int32 = Ok; getProxies = Proxies; //@description Returns an HTTPS link, which can be used to add a proxy. Available only for SOCKS5 and MTProto proxies. Can be called before authorization @proxy_id Proxy identifier -getProxyLink proxy_id:int32 = Text; +getProxyLink proxy_id:int32 = HttpUrl; //@description Computes time needed to receive a response from a Telegram server through a proxy. Can be called before authorization @proxy_id Proxy identifier. Use 0 to ping a Telegram server without a proxy pingProxy proxy_id:int32 = Seconds; @@ -5029,7 +5209,7 @@ setLogTagVerbosityLevel tag:string new_verbosity_level:int32 = Ok; getLogTagVerbosityLevel tag:string = LogVerbosityLevel; //@description Adds a message to TDLib internal log. Can be called synchronously -//@verbosity_level The minimum verbosity level needed for the message to be logged, 0-1023 @text Text of a message to log +//@verbosity_level The minimum verbosity level needed for the message to be logged; 0-1023 @text Text of a message to log addLogMessage verbosity_level:int32 text:string = Ok; diff --git a/td/generate/scheme/telegram_api.tl b/td/generate/scheme/telegram_api.tl index 60018bf81..d37b95efc 100644 --- a/td/generate/scheme/telegram_api.tl +++ b/td/generate/scheme/telegram_api.tl @@ -96,7 +96,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#200250ba id:int = User; -user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; +user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto; @@ -111,11 +111,11 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#9ba2d800 id:int = Chat; chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#7328bdb id:int title:string = Chat; -channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#dc8c181 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall = ChatFull; -channelFull#ef3a6acd flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall = ChatFull; +chatFull#f06c4018 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int = ChatFull; +channelFull#2548c037 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -127,9 +127,9 @@ chatParticipants#3f460fed chat_id:int participants:Vector versi chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; -messageEmpty#83e5de54 id:int = Message; -message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; -messageService#286fa604 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction = Message; +messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; +message#bce383d2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; +messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; @@ -171,6 +171,7 @@ messageActionContactSignUp#f3f25f76 = MessageAction; messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = MessageAction; messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int = MessageAction; messageActionInviteToGroupCall#76b9f11a call:InputGroupCall users:Vector = MessageAction; +messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction; dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -204,7 +205,7 @@ inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings; -peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true geo_distance:flags.6?int = PeerSettings; +peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true geo_distance:flags.6?int = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; @@ -213,11 +214,12 @@ inputReportReasonSpam#58dbcab8 = ReportReason; inputReportReasonViolence#1e22c78d = ReportReason; inputReportReasonPornography#2e59d922 = ReportReason; inputReportReasonChildAbuse#adf44ee3 = ReportReason; -inputReportReasonOther#e1746d0a text:string = ReportReason; +inputReportReasonOther#c1e4a2b1 = ReportReason; inputReportReasonCopyright#9b89f93a = ReportReason; inputReportReasonGeoIrrelevant#dbd4feed = ReportReason; +inputReportReasonFake#f5ddd6e7 = ReportReason; -userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull; +userFull#139a9a77 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int = UserFull; contact#f911c994 user_id:int mutual:Bool = Contact; @@ -344,7 +346,6 @@ updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update; updateDialogFilterOrder#a5d72105 order:Vector = Update; updateDialogFilters#3504914f = Update; updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update; -updateChannelParticipant#65d2b464 flags:# channel_id:int date:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant qts:int = Update; updateChannelMessageForwards#6e8a84df channel_id:int id:int forwards:int = Update; updateReadChannelDiscussionInbox#1cc7de54 flags:# channel_id:int top_msg_id:int read_max_id:int broadcast_id:flags.0?int broadcast_post:flags.0?int = Update; updateReadChannelDiscussionOutbox#4638a26c channel_id:int top_msg_id:int read_max_id:int = Update; @@ -355,6 +356,10 @@ updatePinnedChannelMessages#8588878b flags:# pinned:flags.0?true channel_id:int updateChat#1330a196 chat_id:int = Update; updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector version:int = Update; updateGroupCall#a45eb99b chat_id:int call:GroupCall = Update; +updatePeerHistoryTTL#bb9bb9a5 flags:# peer:Peer ttl_period:flags.0?int = Update; +updateChatParticipant#f3b3781f flags:# chat_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChatParticipant new_participant:flags.1?ChatParticipant invite:flags.2?ExportedChatInvite qts:int = Update; +updateChannelParticipant#7fecb1ec flags:# channel_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant invite:flags.2?ExportedChatInvite qts:int = Update; +updateBotStopped#7f9488a user_id:int date:int stopped:Bool qts:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -364,12 +369,12 @@ updates.differenceSlice#a8fb1981 new_messages:Vector new_encrypted_mess updates.differenceTooLong#4afe8f6d pts:int = updates.Difference; updatesTooLong#e317af7e = Updates; -updateShortMessage#2296d2c8 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector = Updates; -updateShortChatMessage#402d5dbb flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector = Updates; +updateShortMessage#faeff833 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector ttl_period:flags.25?int = Updates; +updateShortChatMessage#1157b858 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector ttl_period:flags.25?int = Updates; updateShort#78d4dec1 update:Update date:int = Updates; updatesCombined#725b04c3 updates:Vector users:Vector chats:Vector date:int seq_start:int seq:int = Updates; updates#74ae4240 updates:Vector users:Vector chats:Vector date:int seq:int = Updates; -updateShortSentMessage#11f1331c flags:# out:flags.1?true id:int pts:int pts_count:int date:int media:flags.9?MessageMedia entities:flags.7?Vector = Updates; +updateShortSentMessage#9015e101 flags:# out:flags.1?true id:int pts:int pts_count:int date:int media:flags.9?MessageMedia entities:flags.7?Vector ttl_period:flags.25?int = Updates; photos.photos#8dca6aa5 photos:Vector users:Vector = photos.Photos; photos.photosSlice#15051f54 count:int photos:Vector users:Vector = photos.Photos; @@ -394,7 +399,7 @@ encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat; encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat; encryptedChatRequested#62718a82 flags:# folder_id:flags.0?int id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat; encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat; -encryptedChatDiscarded#13d6dd27 id:int = EncryptedChat; +encryptedChatDiscarded#1e1c7c45 flags:# history_deleted:flags.0?true id:int = EncryptedChat; inputEncryptedChat#f141b5e1 chat_id:int access_hash:long = InputEncryptedChat; @@ -442,6 +447,7 @@ sendMessageGamePlayAction#dd6a8f48 = SendMessageAction; sendMessageRecordRoundAction#88f27fbc = SendMessageAction; sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction; speakingInGroupCallAction#d92c2285 = SendMessageAction; +sendMessageHistoryImportAction#dbda9246 progress:int = SendMessageAction; contacts.found#b3134d9d my_results:Vector results:Vector chats:Vector users:Vector = contacts.Found; @@ -522,8 +528,7 @@ auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage; -chatInviteEmpty#69df3769 = ExportedChatInvite; -chatInviteExported#fc2e05bc link:string = ExportedChatInvite; +chatInviteExported#6e24fc9d flags:# revoked:flags.0?true permanent:flags.5?true link:string admin_id:int date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; @@ -648,7 +653,7 @@ messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_off exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink; -messageFwdHeader#5f777dce flags:# from_id:flags.0?Peer from_name:flags.5?string date:int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader; +messageFwdHeader#5f777dce flags:# imported:flags.7?true from_id:flags.0?Peer from_name:flags.5?string date:int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader; auth.codeTypeSms#72a3158c = auth.CodeType; auth.codeTypeCall#741cd3e3 = auth.CodeType; @@ -807,7 +812,7 @@ payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_inf inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials; inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials; inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials; -inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials; +inputPaymentCredentialsGooglePay#8ac32801 payment_token:DataJSON = InputPaymentCredentials; account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPassword; @@ -872,12 +877,18 @@ channelAdminLogEventActionDiscardGroupCall#db9f9140 call:InputGroupCall = Channe channelAdminLogEventActionParticipantMute#f92424d2 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantUnmute#e64429c0 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionToggleGroupCallSetting#56d6a247 join_muted:Bool = ChannelAdminLogEventAction; +channelAdminLogEventActionParticipantJoinByInvite#5cdada77 invite:ExportedChatInvite = ChannelAdminLogEventAction; +channelAdminLogEventActionExportedInviteDelete#5a50fca4 invite:ExportedChatInvite = ChannelAdminLogEventAction; +channelAdminLogEventActionExportedInviteRevoke#410a134e invite:ExportedChatInvite = ChannelAdminLogEventAction; +channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatInvite new_invite:ExportedChatInvite = ChannelAdminLogEventAction; +channelAdminLogEventActionParticipantVolume#3e7f6847 participant:GroupCallParticipant = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int = ChannelAdminLogEventAction; channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent; channels.adminLogResults#ed8af74d events:Vector chats:Vector users:Vector = channels.AdminLogResults; -channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true = ChannelAdminLogEventsFilter; +channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true = ChannelAdminLogEventsFilter; popularContact#5ce14175 client_id:long importers:int = PopularContact; @@ -1035,7 +1046,7 @@ chatOnlines#f041e250 onlines:int = ChatOnlines; statsURL#47a971e0 url:string = StatsURL; -chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true = ChatAdminRights; +chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true = ChatAdminRights; chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true until_date:int = ChatBannedRights; @@ -1182,7 +1193,7 @@ groupCall#55903081 flags:# join_muted:flags.1?true can_change_join_muted:flags.2 inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; -groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant; +groupCallParticipant#64c62a15 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true user_id:int date:int active_date:flags.3?int source:int volume:flags.7?int = GroupCallParticipant; phone.groupCall#66ab0bfc call:GroupCall participants:Vector participants_next_offset:string users:Vector = phone.GroupCall; @@ -1194,6 +1205,27 @@ inlineQueryPeerTypeChat#d766c50a = InlineQueryPeerType; inlineQueryPeerTypeMegagroup#5ec4be43 = InlineQueryPeerType; inlineQueryPeerTypeBroadcast#6334ee9a = InlineQueryPeerType; +messages.historyImport#1662af0b id:long = messages.HistoryImport; + +messages.historyImportParsed#5e0fb7b9 flags:# pm:flags.0?true group:flags.1?true title:flags.2?string = messages.HistoryImportParsed; + +messages.affectedFoundMessages#ef8d3e6c pts:int pts_count:int offset:int messages:Vector = messages.AffectedFoundMessages; + +chatInviteImporter#1e3e6680 user_id:int date:int = ChatInviteImporter; + +messages.exportedChatInvites#bdc62dcc count:int invites:Vector users:Vector = messages.ExportedChatInvites; + +messages.exportedChatInvite#1871be50 invite:ExportedChatInvite users:Vector = messages.ExportedChatInvite; +messages.exportedChatInviteReplaced#222600ef invite:ExportedChatInvite new_invite:ExportedChatInvite users:Vector = messages.ExportedChatInvite; + +messages.chatInviteImporters#81b6b00a count:int importers:Vector users:Vector = messages.ChatInviteImporters; + +chatAdminWithInvites#dfd2330f admin_id:int invites_count:int revoked_invites_count:int = ChatAdminWithInvites; + +messages.chatAdminsWithInvites#b69b72d7 admins:Vector users:Vector = messages.ChatAdminsWithInvites; + +messages.checkedHistoryImportPeer#a24de717 confirm_text:string = messages.CheckedHistoryImportPeer; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1231,7 +1263,7 @@ account.resetNotifySettings#db7e1747 = Bool; account.updateProfile#78515775 flags:# first_name:flags.0?string last_name:flags.1?string about:flags.2?string = User; account.updateStatus#6628562c offline:Bool = Bool; account.getWallPapers#aabb1763 hash:int = account.WallPapers; -account.reportPeer#ae189d5f peer:InputPeer reason:ReportReason = Bool; +account.reportPeer#c5ba3d86 peer:InputPeer reason:ReportReason message:string = Bool; account.checkUsername#2714d86c username:string = Bool; account.updateUsername#3e0bdd7c username:string = User; account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules; @@ -1290,6 +1322,7 @@ account.getContentSettings#8b9b4dae = account.ContentSettings; account.getMultiWallPapers#65ad71dc wallpapers:Vector = Vector; account.getGlobalPrivacySettings#eb2b4cf6 = GlobalPrivacySettings; account.setGlobalPrivacySettings#1edaaac2 settings:GlobalPrivacySettings = GlobalPrivacySettings; +account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:ReportReason message:string = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; @@ -1330,18 +1363,18 @@ messages.sendMedia#3491eba9 flags:# silent:flags.5?true background:flags.6?true messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings; -messages.report#bd82b658 peer:InputPeer id:Vector reason:ReportReason = Bool; +messages.report#8953ab4e peer:InputPeer id:Vector reason:ReportReason message:string = Bool; messages.getChats#3c6aa187 id:Vector = messages.Chats; messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull; messages.editChatTitle#dc452855 chat_id:int title:string = Updates; messages.editChatPhoto#ca4c79d8 chat_id:int photo:InputChatPhoto = Updates; messages.addChatUser#f9a0aa09 chat_id:int user_id:InputUser fwd_limit:int = Updates; -messages.deleteChatUser#e0611f16 chat_id:int user_id:InputUser = Updates; +messages.deleteChatUser#c534459a flags:# revoke_history:flags.0?true chat_id:int user_id:InputUser = Updates; messages.createChat#9cb126e users:Vector title:string = Updates; messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig; messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat; messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat; -messages.discardEncryption#edd923c5 chat_id:int = Bool; +messages.discardEncryption#f393aea0 flags:# delete_history:flags.0?true chat_id:int = Bool; messages.setEncryptedTyping#791451ed peer:InputEncryptedChat typing:Bool = Bool; messages.readEncryptedHistory#7f4b690a peer:InputEncryptedChat max_date:int = Bool; messages.sendEncrypted#44fa7a15 flags:# silent:flags.0?true peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; @@ -1353,7 +1386,7 @@ messages.readMessageContents#36a73f77 id:Vector = messages.AffectedMessages messages.getStickers#43d4f2c emoticon:string hash:int = messages.Stickers; messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers; messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector = MessageMedia; -messages.exportChatInvite#df7534c peer:InputPeer = ExportedChatInvite; +messages.exportChatInvite#14b9bcd7 flags:# legacy_revoke_permanent:flags.2?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int = ExportedChatInvite; messages.checkChatInvite#3eadb1bb hash:string = ChatInvite; messages.importChatInvite#6c50051c hash:string = Updates; messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet; @@ -1443,6 +1476,21 @@ messages.getReplies#24b581ba peer:InputPeer msg_id:int offset_id:int offset_date messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage; messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool; messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory; +messages.deleteChat#83247d11 chat_id:int = Bool; +messages.deletePhoneCallHistory#f9cbe409 flags:# revoke:flags.0?true = messages.AffectedFoundMessages; +messages.checkHistoryImport#43fe19f3 import_head:string = messages.HistoryImportParsed; +messages.initHistoryImport#34090c3b peer:InputPeer file:InputFile media_count:int = messages.HistoryImport; +messages.uploadImportedMedia#2a862092 peer:InputPeer import_id:long file_name:string media:InputMedia = MessageMedia; +messages.startHistoryImport#b43df344 peer:InputPeer import_id:long = Bool; +messages.getExportedChatInvites#a2b5a3f6 flags:# revoked:flags.3?true peer:InputPeer admin_id:InputUser offset_date:flags.2?int offset_link:flags.2?string limit:int = messages.ExportedChatInvites; +messages.getExportedChatInvite#73746f5c peer:InputPeer link:string = messages.ExportedChatInvite; +messages.editExportedChatInvite#2e4ffbe flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int = messages.ExportedChatInvite; +messages.deleteRevokedExportedChatInvites#56987bd5 peer:InputPeer admin_id:InputUser = Bool; +messages.deleteExportedChatInvite#d464a42b peer:InputPeer link:string = Bool; +messages.getAdminsWithInvites#3920e6ef peer:InputPeer = messages.ChatAdminsWithInvites; +messages.getChatInviteImporters#26fb7289 peer:InputPeer link:string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters; +messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates; +messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1482,7 +1530,7 @@ help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo; help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector = help.UserInfo; help.getPromoData#c0977421 = help.PromoData; help.hidePromoData#1e251c95 peer:InputPeer = Bool; -help.dismissSuggestion#77fa99f suggestion:string = Bool; +help.dismissSuggestion#f50dbaa1 peer:InputPeer suggestion:string = Bool; help.getCountriesList#735787a8 lang_code:string hash:int = help.CountriesList; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; @@ -1494,7 +1542,7 @@ channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipant channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant; channels.getChannels#a7f6bbb id:Vector = messages.Chats; channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull; -channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates; +channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates; channels.editAdmin#d33c8902 channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights rank:string = Updates; channels.editTitle#566decd0 channel:InputChannel title:string = Updates; channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates; @@ -1520,6 +1568,7 @@ channels.editCreator#8f38cd1f channel:InputChannel user_id:InputUser password:In channels.editLocation#58e63f6d channel:InputChannel geo_point:InputGeoPoint address:string = Bool; channels.toggleSlowMode#edd49ef0 channel:InputChannel seconds:int = Updates; channels.getInactiveChannels#11e831ee = messages.InactiveChats; +channels.convertToGigagroup#b290c69 channel:InputChannel = Updates; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1551,7 +1600,7 @@ phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; phone.createGroupCall#bd3dabe0 peer:InputPeer random_id:int = Updates; phone.joinGroupCall#5f9c8e62 flags:# muted:flags.0?true call:InputGroupCall params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; -phone.editGroupCallMember#63146ae4 flags:# muted:flags.0?true call:InputGroupCall user_id:InputUser = Updates; +phone.editGroupCallMember#a5e76cd8 flags:# muted:flags.0?true call:InputGroupCall user_id:InputUser volume:flags.1?int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; phone.toggleGroupCallSettings#74bbb43d flags:# call:InputGroupCall join_muted:flags.0?Bool = Updates; diff --git a/td/generate/tl-parser b/td/generate/tl-parser deleted file mode 160000 index 3df9f70d5..000000000 --- a/td/generate/tl-parser +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3df9f70d5947282836ea454609fc6dcc1e8f546c diff --git a/td/mtproto/AuthKey.h b/td/mtproto/AuthKey.h index 5c3959605..20eb414e2 100644 --- a/td/mtproto/AuthKey.h +++ b/td/mtproto/AuthKey.h @@ -34,11 +34,7 @@ class AuthKey { bool auth_flag() const { return auth_flag_; } - bool was_auth_flag() const { - return was_auth_flag_; - } void set_auth_flag(bool new_auth_flag) { - was_auth_flag_ |= new_auth_flag; auth_flag_ = new_auth_flag; } @@ -67,15 +63,13 @@ class AuthKey { } static constexpr int32 AUTH_FLAG = 1; - static constexpr int32 WAS_AUTH_FLAG = 2; static constexpr int32 HAS_CREATED_AT = 4; template void store(StorerT &storer) const { storer.store_binary(auth_key_id_); bool has_created_at = created_at_ != 0; - storer.store_binary(static_cast((auth_flag_ ? AUTH_FLAG : 0) | (was_auth_flag_ ? WAS_AUTH_FLAG : 0) | - (has_created_at ? HAS_CREATED_AT : 0))); + storer.store_binary(static_cast((auth_flag_ ? AUTH_FLAG : 0) | (has_created_at ? HAS_CREATED_AT : 0))); storer.store_string(auth_key_); if (has_created_at) { storer.store_binary(created_at_); @@ -87,7 +81,6 @@ class AuthKey { auth_key_id_ = parser.fetch_long(); auto flags = parser.fetch_int(); auth_flag_ = (flags & AUTH_FLAG) != 0; - was_auth_flag_ = (flags & WAS_AUTH_FLAG) != 0 || auth_flag_; auth_key_ = parser.template fetch_string(); if ((flags & HAS_CREATED_AT) != 0) { created_at_ = parser.fetch_double(); @@ -100,7 +93,6 @@ class AuthKey { uint64 auth_key_id_{0}; string auth_key_; bool auth_flag_{false}; - bool was_auth_flag_{false}; bool need_header_{true}; double expires_at_{0}; double created_at_{0}; diff --git a/td/mtproto/ProxySecret.h b/td/mtproto/ProxySecret.h index 6bb1e441a..fb88ff25c 100644 --- a/td/mtproto/ProxySecret.h +++ b/td/mtproto/ProxySecret.h @@ -32,9 +32,9 @@ class ProxySecret { } Slice get_proxy_secret() const { - auto proxy_secret = Slice(secret_).truncate(17); - if (proxy_secret.size() == 17) { - proxy_secret.remove_prefix(1); + Slice proxy_secret(secret_); + if (proxy_secret.size() >= 17) { + return proxy_secret.substr(1, 16); } return proxy_secret; } diff --git a/td/mtproto/RSA.cpp b/td/mtproto/RSA.cpp index 5c33f3bd5..48740130a 100644 --- a/td/mtproto/RSA.cpp +++ b/td/mtproto/RSA.cpp @@ -22,7 +22,7 @@ #include #include #include -#if OPENSSL_VERSION_NUMBER < 0x30000000L +#if OPENSSL_VERSION_NUMBER < 0x30000000L || defined(LIBRESSL_VERSION_NUMBER) #include #endif @@ -47,7 +47,7 @@ Result RSA::from_pem_public_key(Slice pem) { BIO_free(bio); }; -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) EVP_PKEY *rsa = PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr); #else auto rsa = PEM_read_bio_RSAPublicKey(bio, nullptr, nullptr, nullptr); @@ -56,14 +56,14 @@ Result RSA::from_pem_public_key(Slice pem) { return Status::Error("Error while reading RSA public key"); } SCOPE_EXIT { -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) EVP_PKEY_free(rsa); #else RSA_free(rsa); #endif }; -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) if (!EVP_PKEY_is_a(rsa, "RSA")) { return Status::Error("Key is not an RSA key"); } @@ -76,7 +76,7 @@ Result RSA::from_pem_public_key(Slice pem) { } #endif -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) BIGNUM *n_num = nullptr; BIGNUM *e_num = nullptr; diff --git a/td/mtproto/TcpTransport.cpp b/td/mtproto/TcpTransport.cpp index 70546ec1c..98760aa1e 100644 --- a/td/mtproto/TcpTransport.cpp +++ b/td/mtproto/TcpTransport.cpp @@ -63,7 +63,7 @@ void IntermediateTransport::write_prepare_inplace(BufferWriter *message, bool qu size_t append_size = 0; if (with_padding()) { append_size = Random::secure_uint32() % 16; - MutableSlice append = message->prepare_append().truncate(append_size); + MutableSlice append = message->prepare_append().substr(0, append_size); CHECK(append.size() == append_size); Random::secure_bytes(append); message->confirm_append(append.size()); diff --git a/td/mtproto/Transport.cpp b/td/mtproto/Transport.cpp index 782aa8683..7aa616622 100644 --- a/td/mtproto/Transport.cpp +++ b/td/mtproto/Transport.cpp @@ -226,7 +226,7 @@ Status Transport::read_crypto_impl(int X, MutableSlice message, const AuthKey &a auto *header = reinterpret_cast(message.begin()); *header_ptr = header; auto to_decrypt = MutableSlice(header->encrypt_begin(), message.uend()); - to_decrypt = to_decrypt.truncate(to_decrypt.size() & ~15); + to_decrypt.truncate(to_decrypt.size() & ~15); if (to_decrypt.size() % 16 != 0) { return Status::Error(PSLICE() << "Invalid mtproto message: size of encrypted part is not multiple of 16 [size = " << to_decrypt.size() << "]"); diff --git a/td/mtproto/Transport.h b/td/mtproto/Transport.h index d8875f59a..57eea1a62 100644 --- a/td/mtproto/Transport.h +++ b/td/mtproto/Transport.h @@ -91,11 +91,11 @@ class Transport { static size_t write(const Storer &storer, const AuthKey &auth_key, PacketInfo *info, MutableSlice dest = MutableSlice()); + static std::pair calc_message_key2(const AuthKey &auth_key, int X, Slice to_encrypt); private: template static std::pair calc_message_ack_and_key(const HeaderT &head, size_t data_size); - static std::pair calc_message_key2(const AuthKey &auth_key, int X, Slice to_encrypt); template static size_t calc_crypto_size(size_t data_size); diff --git a/td/telegram/AnimationsManager.cpp b/td/telegram/AnimationsManager.cpp index 3be56900a..caa54c2cb 100644 --- a/td/telegram/AnimationsManager.cpp +++ b/td/telegram/AnimationsManager.cpp @@ -310,6 +310,7 @@ bool AnimationsManager::merge_animations(FileId new_id, FileId old_id, bool can_ return old_->is_changed; } + bool need_merge = true; auto new_it = animations_.find(new_id); if (new_it == animations_.end() || new_it->second == nullptr) { auto &old = animations_[old_id]; @@ -328,8 +329,13 @@ bool AnimationsManager::merge_animations(FileId new_id, FileId old_id, bool can_ if (old_->thumbnail != new_->thumbnail) { // LOG_STATUS(td_->file_manager_->merge(new_->thumbnail.file_id, old_->thumbnail.file_id)); } + if (old_->mime_type == "image/gif" && new_->mime_type == "video/mp4") { + need_merge = false; + } + } + if (need_merge) { + LOG_STATUS(td_->file_manager_->merge(new_id, old_id)); } - LOG_STATUS(td_->file_manager_->merge(new_id, old_id)); if (can_delete_old) { animations_.erase(old_id); } @@ -402,7 +408,7 @@ tl_object_ptr AnimationsManager::get_input_media( } return make_tl_object( flags, false /*ignored*/, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type, - std::move(attributes), vector>(), 0); + std::move(attributes), std::move(added_stickers), 0); } else { CHECK(!file_view.has_remote_location()); } diff --git a/td/telegram/AuthManager.cpp b/td/telegram/AuthManager.cpp index f73da39e5..301105f15 100644 --- a/td/telegram/AuthManager.cpp +++ b/td/telegram/AuthManager.cpp @@ -729,7 +729,7 @@ void AuthManager::on_get_authorization(tl_object_ptrschedule_get_promo_data(0); G()->td_db()->get_binlog_pmc()->set("fetched_marks_as_unread", "1"); } else { - send_closure(G()->state_manager(), &StateManager::on_online, true); + td->set_is_bot_online(true); } send_closure(G()->config_manager(), &ConfigManager::request_config); if (query_id_ != 0) { diff --git a/td/telegram/CallActor.cpp b/td/telegram/CallActor.cpp index ceefd7716..7181834c0 100644 --- a/td/telegram/CallActor.cpp +++ b/td/telegram/CallActor.cpp @@ -189,8 +189,8 @@ void CallActor::send_call_signaling_data(string &&data, Promise<> promise) { auto query = G()->net_query_creator().create( telegram_api::phone_sendSignalingData(get_input_phone_call("send_call_signaling_data"), BufferSlice(data))); send_with_promise(std::move(query), - PromiseCreator::lambda([promise = std::move(promise)](NetQueryPtr net_query) mutable { - auto res = fetch_result(std::move(net_query)); + PromiseCreator::lambda([promise = std::move(promise)](Result r_net_query) mutable { + auto res = fetch_result(std::move(r_net_query)); if (res.is_error()) { promise.set_error(res.move_as_error()); } else { @@ -298,14 +298,15 @@ void CallActor::rate_call(int32 rating, string comment, vectornet_query_creator().create(tl_query); - send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) { - send_closure(actor_id, &CallActor::on_set_rating_query_result, std::move(net_query)); + send_with_promise(std::move(query), + PromiseCreator::lambda([actor_id = actor_id(this)](Result r_net_query) { + send_closure(actor_id, &CallActor::on_set_rating_query_result, std::move(r_net_query)); })); loop(); } -void CallActor::on_set_rating_query_result(NetQueryPtr net_query) { - auto res = fetch_result(std::move(net_query)); +void CallActor::on_set_rating_query_result(Result r_net_query) { + auto res = fetch_result(std::move(r_net_query)); if (res.is_error()) { return on_error(res.move_as_error()); } @@ -321,14 +322,15 @@ void CallActor::send_call_debug_information(string data, Promise<> promise) { auto tl_query = telegram_api::phone_saveCallDebug(get_input_phone_call("send_call_debug_information"), make_tl_object(std::move(data))); auto query = G()->net_query_creator().create(tl_query); - send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) { - send_closure(actor_id, &CallActor::on_set_debug_query_result, std::move(net_query)); + send_with_promise(std::move(query), + PromiseCreator::lambda([actor_id = actor_id(this)](Result r_net_query) { + send_closure(actor_id, &CallActor::on_set_debug_query_result, std::move(r_net_query)); })); loop(); } -void CallActor::on_set_debug_query_result(NetQueryPtr net_query) { - auto res = fetch_result(std::move(net_query)); +void CallActor::on_set_debug_query_result(Result r_net_query) { + auto res = fetch_result(std::move(r_net_query)); if (res.is_error()) { return on_error(res.move_as_error()); } @@ -603,13 +605,14 @@ void CallActor::do_load_dh_config(Promise> promise) { void CallActor::send_received_query() { auto tl_query = telegram_api::phone_receivedCall(get_input_phone_call("send_received_query")); auto query = G()->net_query_creator().create(tl_query); - send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) { - send_closure(actor_id, &CallActor::on_received_query_result, std::move(net_query)); + send_with_promise(std::move(query), + PromiseCreator::lambda([actor_id = actor_id(this)](Result r_net_query) { + send_closure(actor_id, &CallActor::on_received_query_result, std::move(r_net_query)); })); } -void CallActor::on_received_query_result(NetQueryPtr net_query) { - auto res = fetch_result(std::move(net_query)); +void CallActor::on_received_query_result(Result r_net_query) { + auto res = fetch_result(std::move(r_net_query)); if (res.is_error()) { return on_error(res.move_as_error()); } @@ -637,13 +640,14 @@ void CallActor::try_send_request_query() { set_timeout_in(timeout); query->total_timeout_limit_ = max(timeout, 10.0); request_query_ref_ = query.get_weak(); - send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) { - send_closure(actor_id, &CallActor::on_request_query_result, std::move(net_query)); + send_with_promise(std::move(query), + PromiseCreator::lambda([actor_id = actor_id(this)](Result r_net_query) { + send_closure(actor_id, &CallActor::on_request_query_result, std::move(r_net_query)); })); } -void CallActor::on_request_query_result(NetQueryPtr net_query) { - auto res = fetch_result(std::move(net_query)); +void CallActor::on_request_query_result(Result r_net_query) { + auto res = fetch_result(std::move(r_net_query)); if (res.is_error()) { return on_error(res.move_as_error()); } @@ -666,13 +670,14 @@ void CallActor::try_send_accept_query() { call_state_.protocol.get_input_phone_call_protocol()); auto query = G()->net_query_creator().create(tl_query); state_ = State::WaitAcceptResult; - send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) { - send_closure(actor_id, &CallActor::on_accept_query_result, std::move(net_query)); + send_with_promise(std::move(query), + PromiseCreator::lambda([actor_id = actor_id(this)](Result r_net_query) { + send_closure(actor_id, &CallActor::on_accept_query_result, std::move(r_net_query)); })); } -void CallActor::on_accept_query_result(NetQueryPtr net_query) { - auto res = fetch_result(std::move(net_query)); +void CallActor::on_accept_query_result(Result r_net_query) { + auto res = fetch_result(std::move(r_net_query)); if (res.is_error()) { return on_error(res.move_as_error()); } @@ -690,13 +695,14 @@ void CallActor::try_send_confirm_query() { call_state_.protocol.get_input_phone_call_protocol()); auto query = G()->net_query_creator().create(tl_query); state_ = State::WaitConfirmResult; - send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) { - send_closure(actor_id, &CallActor::on_confirm_query_result, std::move(net_query)); + send_with_promise(std::move(query), + PromiseCreator::lambda([actor_id = actor_id(this)](Result r_net_query) { + send_closure(actor_id, &CallActor::on_confirm_query_result, std::move(r_net_query)); })); } -void CallActor::on_confirm_query_result(NetQueryPtr net_query) { - auto res = fetch_result(std::move(net_query)); +void CallActor::on_confirm_query_result(Result r_net_query) { + auto res = fetch_result(std::move(r_net_query)); if (res.is_error()) { return on_error(res.move_as_error()); } @@ -720,13 +726,14 @@ void CallActor::try_send_discard_query() { get_input_phone_call_discard_reason(call_state_.discard_reason), connection_id_); auto query = G()->net_query_creator().create(tl_query); state_ = State::WaitDiscardResult; - send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) { - send_closure(actor_id, &CallActor::on_discard_query_result, std::move(net_query)); + send_with_promise(std::move(query), + PromiseCreator::lambda([actor_id = actor_id(this)](Result r_net_query) { + send_closure(actor_id, &CallActor::on_discard_query_result, std::move(r_net_query)); })); } -void CallActor::on_discard_query_result(NetQueryPtr net_query) { - auto res = fetch_result(std::move(net_query)); +void CallActor::on_discard_query_result(Result r_net_query) { + auto res = fetch_result(std::move(r_net_query)); if (res.is_error()) { return on_error(res.move_as_error()); } @@ -768,13 +775,14 @@ void CallActor::flush_call_state() { void CallActor::start_up() { auto tl_query = telegram_api::phone_getCallConfig(); auto query = G()->net_query_creator().create(tl_query); - send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) { - send_closure(actor_id, &CallActor::on_get_call_config_result, std::move(net_query)); + send_with_promise(std::move(query), + PromiseCreator::lambda([actor_id = actor_id(this)](Result r_net_query) { + send_closure(actor_id, &CallActor::on_get_call_config_result, std::move(r_net_query)); })); } -void CallActor::on_get_call_config_result(NetQueryPtr net_query) { - auto res = fetch_result(std::move(net_query)); +void CallActor::on_get_call_config_result(Result r_net_query) { + auto res = fetch_result(std::move(r_net_query)); if (res.is_error()) { return on_error(res.move_as_error()); } diff --git a/td/telegram/CallActor.h b/td/telegram/CallActor.h index 7d282661f..d4ac600ff 100644 --- a/td/telegram/CallActor.h +++ b/td/telegram/CallActor.h @@ -164,28 +164,28 @@ class CallActor : public NetQueryCallback { Status do_update_call(telegram_api::phoneCallDiscarded &call); void send_received_query(); - void on_received_query_result(NetQueryPtr net_query); + void on_received_query_result(Result r_net_query); void try_send_request_query(); - void on_request_query_result(NetQueryPtr net_query); + void on_request_query_result(Result r_net_query); void try_send_accept_query(); - void on_accept_query_result(NetQueryPtr net_query); + void on_accept_query_result(Result r_net_query); void try_send_confirm_query(); - void on_confirm_query_result(NetQueryPtr net_query); + void on_confirm_query_result(Result r_net_query); void try_send_discard_query(); - void on_discard_query_result(NetQueryPtr net_query); + void on_discard_query_result(Result r_net_query); void on_begin_exchanging_key(); void on_call_discarded(CallDiscardReason reason, bool need_rating, bool need_debug, bool is_video); - void on_set_rating_query_result(NetQueryPtr net_query); - void on_set_debug_query_result(NetQueryPtr net_query); + void on_set_rating_query_result(Result r_net_query); + void on_set_debug_query_result(Result r_net_query); - void on_get_call_config_result(NetQueryPtr net_query); + void on_get_call_config_result(Result r_net_query); void flush_call_state(); diff --git a/td/telegram/ConfigManager.cpp b/td/telegram/ConfigManager.cpp index c5be27e4c..09bdd1e81 100644 --- a/td/telegram/ConfigManager.cpp +++ b/td/telegram/ConfigManager.cpp @@ -45,6 +45,7 @@ #include "td/utils/common.h" #include "td/utils/crypto.h" #include "td/utils/format.h" +#include "td/utils/HttpUrl.h" #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" #include "td/utils/misc.h" @@ -56,7 +57,6 @@ #include "td/utils/tl_parsers.h" #include "td/utils/UInt.h" -#include #include #include #include @@ -100,7 +100,7 @@ Result HttpDate::to_unix_time(int32 year, int32 month, int32 day, int32 h return res; } -Result HttpDate::parse_http_date(std::string slice) { +Result HttpDate::parse_http_date(string slice) { Parser p(slice); p.read_till(','); // ignore week day p.skip(','); @@ -434,10 +434,8 @@ ActorOwn<> get_full_config(DcOption option, Promise promise, ActorSh } return res; } - std::pair get_auth_key_state() override { - auto auth_key = get_auth_key(); - AuthKeyState state = AuthDataShared::get_auth_key_state(auth_key); - return std::make_pair(state, auth_key.was_auth_flag()); + AuthKeyState get_auth_key_state() override { + return AuthDataShared::get_auth_key_state(get_auth_key()); } void set_auth_key(const mtproto::AuthKey &auth_key) override { G()->td_db()->get_binlog_pmc()->set(auth_key_key(), serialize(auth_key)); @@ -890,6 +888,9 @@ void ConfigManager::start_up() { expire_time_ = expire_time; set_timeout_in(expire_time_.in()); } + + autologin_update_time_ = Time::now() - 365 * 86400; + autologin_domains_ = full_split(G()->td_db()->get_binlog_pmc()->get("autologin_domains"), '\xFF'); } ActorShared<> ConfigManager::create_reference() { @@ -966,6 +967,60 @@ void ConfigManager::get_app_config(Promise } } +void ConfigManager::get_external_link(string &&link, Promise &&promise) { + if (G()->close_flag()) { + return promise.set_value(std::move(link)); + } + + auto r_url = parse_url(link); + if (r_url.is_error()) { + return promise.set_value(std::move(link)); + } + + if (!td::contains(autologin_domains_, r_url.ok().host_)) { + return promise.set_value(std::move(link)); + } + + if (autologin_update_time_ < Time::now() - 10000) { + auto query_promise = PromiseCreator::lambda([link = std::move(link), promise = std::move(promise)]( + Result> &&result) mutable { + if (result.is_error()) { + return promise.set_value(std::move(link)); + } + send_closure(G()->config_manager(), &ConfigManager::get_external_link, std::move(link), std::move(promise)); + }); + return get_app_config(std::move(query_promise)); + } + + if (autologin_token_.empty()) { + return promise.set_value(std::move(link)); + } + + auto url = r_url.move_as_ok(); + url.protocol_ = HttpUrl::Protocol::Https; + Slice path = url.query_; + path.truncate(url.query_.find_first_of("?#")); + Slice parameters_hash = Slice(url.query_).substr(path.size()); + Slice parameters = parameters_hash; + parameters.truncate(parameters.find('#')); + Slice hash = parameters_hash.substr(parameters.size()); + + string added_parameter; + if (parameters.empty()) { + added_parameter = '?'; + } else if (parameters.size() == 1) { + CHECK(parameters == "?"); + } else { + added_parameter = '&'; + } + added_parameter += "autologin_token="; + added_parameter += autologin_token_; + + url.query_ = PSTRING() << path << parameters << added_parameter << hash; + + promise.set_value(url.get_url()); +} + void ConfigManager::get_content_settings(Promise &&promise) { if (G()->close_flag()) { return promise.set_error(Status::Error(500, "Request aborted")); @@ -1025,7 +1080,7 @@ void ConfigManager::set_archive_and_mute(bool archive_and_mute, Promise && return promise.set_error(Status::Error(500, "Request aborted")); } if (archive_and_mute) { - do_dismiss_suggested_action(SuggestedAction::EnableArchiveAndMuteNewChats); + remove_suggested_action(suggested_actions_, SuggestedAction{SuggestedAction::Type::EnableArchiveAndMuteNewChats}); } last_set_archive_and_mute_ = archive_and_mute; @@ -1070,22 +1125,13 @@ void ConfigManager::do_set_ignore_sensitive_content_restrictions(bool ignore_sen void ConfigManager::do_set_archive_and_mute(bool archive_and_mute) { if (archive_and_mute) { - do_dismiss_suggested_action(SuggestedAction::EnableArchiveAndMuteNewChats); + remove_suggested_action(suggested_actions_, SuggestedAction{SuggestedAction::Type::EnableArchiveAndMuteNewChats}); } G()->shared_config().set_option_boolean("archive_and_mute_new_chats_from_unknown_users", archive_and_mute); } -td_api::object_ptr ConfigManager::get_update_suggested_actions( - const vector &added_actions, const vector &removed_actions) { - return td_api::make_object(transform(added_actions, get_suggested_action_object), - transform(removed_actions, get_suggested_action_object)); -} - void ConfigManager::dismiss_suggested_action(SuggestedAction suggested_action, Promise &&promise) { - if (suggested_action == SuggestedAction::Empty) { - return promise.set_error(Status::Error(400, "Action must be non-empty")); - } - auto action_str = get_suggested_action_str(suggested_action); + auto action_str = suggested_action.get_suggested_action_str(); if (action_str.empty()) { return promise.set_value(Unit()); } @@ -1095,27 +1141,24 @@ void ConfigManager::dismiss_suggested_action(SuggestedAction suggested_action, P } dismiss_suggested_action_request_count_++; - auto &queries = dismiss_suggested_action_queries_[suggested_action]; + auto type = static_cast(suggested_action.type_); + auto &queries = dismiss_suggested_action_queries_[type]; queries.push_back(std::move(promise)); if (queries.size() == 1) { G()->net_query_dispatcher().dispatch_with_callback( - G()->net_query_creator().create(telegram_api::help_dismissSuggestion(action_str)), - actor_shared(this, 100 + static_cast(suggested_action))); - } -} - -void ConfigManager::do_dismiss_suggested_action(SuggestedAction suggested_action) { - if (td::remove(suggested_actions_, suggested_action)) { - send_closure(G()->td(), &Td::send_update, get_update_suggested_actions({}, {suggested_action})); + G()->net_query_creator().create( + telegram_api::help_dismissSuggestion(make_tl_object(), action_str)), + actor_shared(this, 100 + type)); } } void ConfigManager::on_result(NetQueryPtr res) { auto token = get_link_token(); if (token >= 100 && token <= 200) { - SuggestedAction suggested_action = static_cast(static_cast(token - 100)); - auto promises = std::move(dismiss_suggested_action_queries_[suggested_action]); - dismiss_suggested_action_queries_.erase(suggested_action); + auto type = static_cast(token - 100); + SuggestedAction suggested_action{static_cast(type)}; + auto promises = std::move(dismiss_suggested_action_queries_[type]); + dismiss_suggested_action_queries_.erase(type); CHECK(!promises.empty()); CHECK(dismiss_suggested_action_request_count_ >= promises.size()); dismiss_suggested_action_request_count_ -= promises.size(); @@ -1127,7 +1170,7 @@ void ConfigManager::on_result(NetQueryPtr res) { } return; } - do_dismiss_suggested_action(suggested_action); + remove_suggested_action(suggested_actions_, suggested_action); get_app_config(Auto()); for (auto &promise : promises) { @@ -1476,6 +1519,11 @@ void ConfigManager::process_app_config(tl_object_ptr &c const bool archive_and_mute = G()->shared_config().get_option_boolean("archive_and_mute_new_chats_from_unknown_users"); + autologin_token_.clear(); + auto old_autologin_domains = std::move(autologin_domains_); + autologin_domains_.clear(); + autologin_update_time_ = Time::now(); + vector> new_values; string ignored_restriction_reasons; vector dice_emojis; @@ -1613,10 +1661,11 @@ void ConfigManager::process_app_config(tl_object_ptr &c CHECK(action != nullptr); if (action->get_id() == telegram_api::jsonString::ID) { Slice action_str = static_cast(action.get())->value_; - auto suggested_action = get_suggested_action(action_str); - if (suggested_action != SuggestedAction::Empty) { - if (archive_and_mute && suggested_action == SuggestedAction::EnableArchiveAndMuteNewChats) { - LOG(INFO) << "Skip SuggestedAction::EnableArchiveAndMuteNewChats"; + SuggestedAction suggested_action(action_str); + if (!suggested_action.is_empty()) { + if (archive_and_mute && + suggested_action == SuggestedAction{SuggestedAction::Type::EnableArchiveAndMuteNewChats}) { + LOG(INFO) << "Skip EnableArchiveAndMuteNewChats suggested action"; } else { suggested_actions.push_back(suggested_action); } @@ -1641,6 +1690,30 @@ void ConfigManager::process_app_config(tl_object_ptr &c } continue; } + if (key == "autologin_token") { + if (value->get_id() == telegram_api::jsonString::ID) { + autologin_token_ = url_encode(static_cast(value)->value_); + } else { + LOG(ERROR) << "Receive unexpected autologin_token " << to_string(*value); + } + continue; + } + if (key == "autologin_domains") { + if (value->get_id() == telegram_api::jsonArray::ID) { + auto domains = std::move(static_cast(value)->value_); + for (auto &domain : domains) { + CHECK(domain != nullptr); + if (domain->get_id() == telegram_api::jsonString::ID) { + autologin_domains_.push_back(std::move(static_cast(domain.get())->value_)); + } else { + LOG(ERROR) << "Receive unexpected autologin domain " << to_string(domain); + } + } + } else { + LOG(ERROR) << "Receive unexpected autologin_domains " << to_string(*value); + } + continue; + } new_values.push_back(std::move(key_value)); } @@ -1649,6 +1722,10 @@ void ConfigManager::process_app_config(tl_object_ptr &c } config = make_tl_object(std::move(new_values)); + if (autologin_domains_ != old_autologin_domains) { + G()->td_db()->get_binlog_pmc()->set("autologin_domains", implode(autologin_domains_, '\xFF')); + } + ConfigShared &shared_config = G()->shared_config(); if (ignored_restriction_reasons.empty()) { @@ -1700,34 +1777,13 @@ void ConfigManager::process_app_config(tl_object_ptr &c // do not update suggested actions while changing content settings or dismissing an action if (!is_set_content_settings_request_sent_ && dismiss_suggested_action_request_count_ == 0) { - td::unique(suggested_actions); - if (suggested_actions != suggested_actions_) { - vector added_actions; - vector removed_actions; - auto old_it = suggested_actions_.begin(); - auto new_it = suggested_actions.begin(); - while (old_it != suggested_actions_.end() || new_it != suggested_actions.end()) { - if (old_it != suggested_actions_.end() && - (new_it == suggested_actions.end() || std::less()(*old_it, *new_it))) { - removed_actions.push_back(*old_it++); - } else if (old_it == suggested_actions_.end() || std::less()(*new_it, *old_it)) { - added_actions.push_back(*new_it++); - } else { - old_it++; - new_it++; - } - } - CHECK(!added_actions.empty() || !removed_actions.empty()); - suggested_actions_ = std::move(suggested_actions); - send_closure(G()->td(), &Td::send_update, - get_update_suggested_actions(std::move(added_actions), std::move(removed_actions))); - } + update_suggested_actions(suggested_actions_, std::move(suggested_actions)); } } void ConfigManager::get_current_state(vector> &updates) const { if (!suggested_actions_.empty()) { - updates.push_back(get_update_suggested_actions(suggested_actions_, {})); + updates.push_back(get_update_suggested_actions_object(suggested_actions_, {})); } } diff --git a/td/telegram/ConfigManager.h b/td/telegram/ConfigManager.h index 0b12e4c70..ecc3569c6 100644 --- a/td/telegram/ConfigManager.h +++ b/td/telegram/ConfigManager.h @@ -93,6 +93,8 @@ class ConfigManager : public NetQueryCallback { void get_app_config(Promise> &&promise); + void get_external_link(string &&link, Promise &&promise); + void get_content_settings(Promise &&promise); void set_content_settings(bool ignore_sensitive_content_restrictions, Promise &&promise); @@ -114,6 +116,10 @@ class ConfigManager : public NetQueryCallback { int ref_cnt_{1}; Timestamp expire_time_; + string autologin_token_; + vector autologin_domains_; + double autologin_update_time_ = 0.0; + FloodControlStrict lazy_request_flood_control_; vector>> get_app_config_queries_; @@ -130,7 +136,7 @@ class ConfigManager : public NetQueryCallback { vector suggested_actions_; size_t dismiss_suggested_action_request_count_ = 0; - std::map>> dismiss_suggested_action_queries_; + std::map>> dismiss_suggested_action_queries_; static constexpr uint64 REFCNT_TOKEN = std::numeric_limits::max() - 2; @@ -151,11 +157,6 @@ class ConfigManager : public NetQueryCallback { void do_set_archive_and_mute(bool archive_and_mute); - static td_api::object_ptr get_update_suggested_actions( - const vector &added_actions, const vector &removed_actions); - - void do_dismiss_suggested_action(SuggestedAction suggested_action); - static Timestamp load_config_expire_time(); static void save_config_expire(Timestamp timestamp); static void save_dc_options_update(DcOptions dc_options); diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index b4add6075..09307e132 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -11,9 +11,12 @@ #include "td/telegram/telegram_api.hpp" #include "td/telegram/AuthManager.h" +#include "td/telegram/ConfigManager.h" #include "td/telegram/ConfigShared.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DeviceTokenManager.h" +#include "td/telegram/DialogInviteLink.h" +#include "td/telegram/DialogLocation.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileType.h" @@ -24,6 +27,7 @@ #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/logevent/LogEventHelper.h" #include "td/telegram/MessagesManager.h" +#include "td/telegram/MessageTtlSetting.h" #include "td/telegram/misc.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/NotificationManager.h" @@ -31,6 +35,7 @@ #include "td/telegram/Photo.h" #include "td/telegram/Photo.hpp" #include "td/telegram/SecretChatActor.h" +#include "td/telegram/SecretChatsManager.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/StickerSetId.hpp" #include "td/telegram/StickersManager.h" @@ -54,6 +59,7 @@ #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Random.h" +#include "td/utils/Slice.h" #include "td/utils/StringBuilder.h" #include "td/utils/Time.h" #include "td/utils/tl_helpers.h" @@ -66,6 +72,38 @@ namespace td { +class DismissSuggestionQuery : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit DismissSuggestionQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(SuggestedAction action) { + dialog_id_ = action.dialog_id_; + auto input_peer = td->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); + CHECK(input_peer != nullptr); + + send_query(G()->net_query_creator().create( + telegram_api::help_dismissSuggestion(std::move(input_peer), action.get_suggested_action_str()))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + promise_.set_value(Unit()); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "DismissSuggestionQuery"); + promise_.set_error(std::move(status)); + } +}; + class SetAccountTtlQuery : public Td::ResultHandler { Promise promise_; @@ -1150,13 +1188,13 @@ class ToggleChannelSignaturesQuery : public Td::ResultHandler { } }; -class ToggleChannelIsAllHistoryAvailableQuery : public Td::ResultHandler { +class TogglePrehistoryHiddenQuery : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; bool is_all_history_available_; public: - explicit ToggleChannelIsAllHistoryAvailableQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit TogglePrehistoryHiddenQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id, bool is_all_history_available) { @@ -1176,7 +1214,7 @@ class ToggleChannelIsAllHistoryAvailableQuery : public Td::ResultHandler { } auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for TogglePreHistoryHiddenQuery: " << to_string(ptr); + LOG(INFO) << "Receive result for TogglePrehistoryHiddenQuery: " << to_string(ptr); td->updates_manager_->on_get_updates( std::move(ptr), @@ -1197,7 +1235,46 @@ class ToggleChannelIsAllHistoryAvailableQuery : public Td::ResultHandler { return; } } else { - td->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelIsAllHistoryAvailableQuery"); + td->contacts_manager_->on_get_channel_error(channel_id_, status, "TogglePrehistoryHiddenQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ConvertToGigagroupQuery : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit ConvertToGigagroupQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id) { + channel_id_ = channel_id; + + auto input_channel = td->contacts_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create(telegram_api::channels_convertToGigagroup(std::move(input_channel)))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for ConvertToGigagroupQuery: " << to_string(ptr); + + td->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(uint64 id, Status status) override { + if (status.message() == "CHAT_NOT_MODIFIED") { + promise_.set_value(Unit()); + return; + } else { + td->contacts_manager_->on_get_channel_error(channel_id_, status, "ConvertToGigagroupQuery"); } promise_.set_error(std::move(status)); } @@ -1438,6 +1515,34 @@ class ReportChannelSpamQuery : public Td::ResultHandler { } }; +class DeleteChatQuery : public Td::ResultHandler { + Promise promise_; + + public: + explicit DeleteChatQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChatId chat_id) { + send_query(G()->net_query_creator().create(telegram_api::messages_deleteChat(chat_id.get()))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + LOG(INFO) << "Receive result for DeleteChatQuery: " << result_ptr.ok(); + td->updates_manager_->get_difference("DeleteChatQuery"); + td->updates_manager_->on_get_updates(make_tl_object(Auto(), Auto(), Auto(), 0, 0), + std::move(promise_)); + } + + void on_error(uint64 id, Status status) override { + promise_.set_error(std::move(status)); + } +}; + class DeleteChannelQuery : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; @@ -1535,21 +1640,35 @@ class EditChatAdminQuery : public Td::ResultHandler { } }; -class ExportChatInviteLinkQuery : public Td::ResultHandler { - Promise promise_; - ChatId chat_id_; +class ExportChatInviteQuery : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; public: - explicit ExportChatInviteLinkQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit ExportChatInviteQuery(Promise> &&promise) + : promise_(std::move(promise)) { } - void send(ChatId chat_id) { - chat_id_ = chat_id; - auto input_peer = td->messages_manager_->get_input_peer(DialogId(chat_id), AccessRights::Read); + void send(DialogId dialog_id, int32 expire_date, int32 usage_limit, bool is_permanent) { + dialog_id_ = dialog_id; + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(0, Status::Error(400, "Can't access the chat")); } - send_query(G()->net_query_creator().create(telegram_api::messages_exportChatInvite(std::move(input_peer)))); + + int32 flags = 0; + if (expire_date > 0) { + flags |= telegram_api::messages_exportChatInvite::EXPIRE_DATE_MASK; + } + if (usage_limit > 0) { + flags |= telegram_api::messages_exportChatInvite::USAGE_LIMIT_MASK; + } + if (is_permanent) { + flags |= telegram_api::messages_exportChatInvite::LEGACY_REVOKE_PERMANENT_MASK; + } + + send_query(G()->net_query_creator().create(telegram_api::messages_exportChatInvite( + flags, false /*ignored*/, std::move(input_peer), expire_date, usage_limit))); } void on_result(uint64 id, BufferSlice packet) override { @@ -1561,65 +1680,464 @@ class ExportChatInviteLinkQuery : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ExportChatInviteQuery: " << to_string(ptr); - td->contacts_manager_->on_get_chat_invite_link(chat_id_, std::move(ptr)); - promise_.set_value(Unit()); + DialogInviteLink invite_link(std::move(ptr)); + if (!invite_link.is_valid()) { + return on_error(id, Status::Error(500, "Receive invalid invite link")); + } + if (invite_link.get_creator_user_id() != td->contacts_manager_->get_my_id()) { + return on_error(id, Status::Error(500, "Receive invalid invite link creator")); + } + if (invite_link.is_permanent()) { + td->contacts_manager_->on_get_permanent_dialog_invite_link(dialog_id_, invite_link); + } + promise_.set_value(invite_link.get_chat_invite_link_object(td->contacts_manager_.get())); } void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "ExportChatInviteQuery"); promise_.set_error(std::move(status)); - td->updates_manager_->get_difference("ExportChatInviteLinkQuery"); } }; -class ExportChannelInviteLinkQuery : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; +class EditChatInviteLinkQuery : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; public: - explicit ExportChannelInviteLinkQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit EditChatInviteLinkQuery(Promise> &&promise) + : promise_(std::move(promise)) { } - void send(ChannelId channel_id) { - channel_id_ = channel_id; - auto input_peer = td->messages_manager_->get_input_peer(DialogId(channel_id), AccessRights::Read); + void send(DialogId dialog_id, const string &invite_link, int32 expire_date, int32 usage_limit) { + dialog_id_ = dialog_id; + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(0, Status::Error(400, "Can't access the chat")); } - send_query(G()->net_query_creator().create(telegram_api::messages_exportChatInvite(std::move(input_peer)))); + + int32 flags = telegram_api::messages_editExportedChatInvite::EXPIRE_DATE_MASK | + telegram_api::messages_editExportedChatInvite::USAGE_LIMIT_MASK; + send_query(G()->net_query_creator().create(telegram_api::messages_editExportedChatInvite( + flags, false /*ignored*/, std::move(input_peer), invite_link, expire_date, usage_limit))); } void on_result(uint64 id, BufferSlice packet) override { - auto result_ptr = fetch_result(packet); + auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(id, result_ptr.move_as_error()); } - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for ExportChannelInviteQuery: " << to_string(ptr); + auto result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for EditChatInviteLinkQuery: " << to_string(result); + + if (result->get_id() != telegram_api::messages_exportedChatInvite::ID) { + return on_error(id, Status::Error(500, "Receive unexpected response from server")); + } + + auto invite = move_tl_object_as(result); + + td->contacts_manager_->on_get_users(std::move(invite->users_), "EditChatInviteLinkQuery"); + + DialogInviteLink invite_link(std::move(invite->invite_)); + if (!invite_link.is_valid()) { + return on_error(id, Status::Error(500, "Receive invalid invite link")); + } + promise_.set_value(invite_link.get_chat_invite_link_object(td->contacts_manager_.get())); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditChatInviteLinkQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetExportedChatInviteQuery : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit GetExportedChatInviteQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, const string &invite_link) { + dialog_id_ = dialog_id; + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + if (input_peer == nullptr) { + return on_error(0, Status::Error(400, "Can't access the chat")); + } + + send_query(G()->net_query_creator().create( + telegram_api::messages_getExportedChatInvite(std::move(input_peer), invite_link))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + if (result_ptr.ok()->get_id() != telegram_api::messages_exportedChatInvite::ID) { + LOG(ERROR) << "Receive wrong result for GetExportedChatInviteQuery: " << to_string(result_ptr.ok()); + return on_error(id, Status::Error(500, "Receive unexpected response")); + } + + auto result = move_tl_object_as(result_ptr.ok_ref()); + LOG(INFO) << "Receive result for GetExportedChatInviteQuery: " << to_string(result); + + td->contacts_manager_->on_get_users(std::move(result->users_), "GetExportedChatInviteQuery"); + + DialogInviteLink invite_link(std::move(result->invite_)); + if (!invite_link.is_valid()) { + LOG(ERROR) << "Receive invalid invite link in " << dialog_id_; + return on_error(id, Status::Error(500, "Receive invalid invite link")); + } + promise_.set_value(invite_link.get_chat_invite_link_object(td->contacts_manager_.get())); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetExportedChatInviteQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetExportedChatInvitesQuery : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit GetExportedChatInvitesQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, UserId creator_user_id, bool is_revoked, int32 offset_date, + const string &offset_invite_link, int32 limit) { + dialog_id_ = dialog_id; + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + if (input_peer == nullptr) { + return on_error(0, Status::Error(400, "Can't access the chat")); + } + + auto input_user = td->contacts_manager_->get_input_user(creator_user_id); + CHECK(input_user != nullptr); + + int32 flags = 0; + if (!offset_invite_link.empty() || offset_date != 0) { + flags |= telegram_api::messages_getExportedChatInvites::OFFSET_DATE_MASK; + flags |= telegram_api::messages_getExportedChatInvites::OFFSET_LINK_MASK; + } + if (is_revoked) { + flags |= telegram_api::messages_getExportedChatInvites::REVOKED_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::messages_getExportedChatInvites(flags, false /*ignored*/, std::move(input_peer), + std::move(input_user), offset_date, offset_invite_link, limit))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + auto result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetExportedChatInvitesQuery: " << to_string(result); + + td->contacts_manager_->on_get_users(std::move(result->users_), "GetExportedChatInvitesQuery"); + + int32 total_count = result->count_; + if (total_count < static_cast(result->invites_.size())) { + LOG(ERROR) << "Receive wrong total count of invite links " << total_count << " in " << dialog_id_; + total_count = static_cast(result->invites_.size()); + } + vector> invite_links; + for (auto &invite : result->invites_) { + DialogInviteLink invite_link(std::move(invite)); + if (!invite_link.is_valid()) { + LOG(ERROR) << "Receive invalid invite link in " << dialog_id_; + total_count--; + continue; + } + invite_links.push_back(invite_link.get_chat_invite_link_object(td->contacts_manager_.get())); + } + promise_.set_value(td_api::make_object(total_count, std::move(invite_links))); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetExportedChatInvitesQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetChatAdminWithInvitesQuery : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit GetChatAdminWithInvitesQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id) { + dialog_id_ = dialog_id; + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + if (input_peer == nullptr) { + return on_error(0, Status::Error(400, "Can't access the chat")); + } + + send_query(G()->net_query_creator().create(telegram_api::messages_getAdminsWithInvites(std::move(input_peer)))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + auto result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetChatAdminWithInvitesQuery: " << to_string(result); + + td->contacts_manager_->on_get_users(std::move(result->users_), "GetChatAdminWithInvitesQuery"); + + vector> invite_link_counts; + for (auto &admin : result->admins_) { + UserId user_id(admin->admin_id_); + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid invite link creator " << user_id << " in " << dialog_id_; + continue; + } + invite_link_counts.push_back(td_api::make_object( + td->contacts_manager_->get_user_id_object(user_id, "chatInviteLinkCount"), admin->invites_count_, + admin->revoked_invites_count_)); + } + promise_.set_value(td_api::make_object(std::move(invite_link_counts))); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetChatAdminWithInvitesQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetChatInviteImportersQuery : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit GetChatInviteImportersQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, const string &invite_link, int32 offset_date, UserId offset_user_id, int32 limit) { + dialog_id_ = dialog_id; + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + if (input_peer == nullptr) { + return on_error(0, Status::Error(400, "Can't access the chat")); + } + + auto input_user = td->contacts_manager_->get_input_user(offset_user_id); + if (input_user == nullptr) { + input_user = make_tl_object(); + } + + send_query(G()->net_query_creator().create(telegram_api::messages_getChatInviteImporters( + std::move(input_peer), invite_link, offset_date, std::move(input_user), limit))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + auto result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetChatInviteImportersQuery: " << to_string(result); + + td->contacts_manager_->on_get_users(std::move(result->users_), "GetChatInviteImportersQuery"); + + int32 total_count = result->count_; + if (total_count < static_cast(result->importers_.size())) { + LOG(ERROR) << "Receive wrong total count of invite link users " << total_count << " in " << dialog_id_; + total_count = static_cast(result->importers_.size()); + } + vector> invite_link_members; + for (auto &importer : result->importers_) { + UserId user_id(importer->user_id_); + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid invite link " << user_id << " in " << dialog_id_; + total_count--; + continue; + } + invite_link_members.push_back(td_api::make_object( + td->contacts_manager_->get_user_id_object(user_id, "chatInviteLinkMember"), importer->date_)); + } + promise_.set_value(td_api::make_object(total_count, std::move(invite_link_members))); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetChatInviteImportersQuery"); + promise_.set_error(std::move(status)); + } +}; + +class RevokeChatInviteLinkQuery : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit RevokeChatInviteLinkQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, const string &invite_link) { + dialog_id_ = dialog_id; + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + if (input_peer == nullptr) { + return on_error(0, Status::Error(400, "Can't access the chat")); + } + + int32 flags = telegram_api::messages_editExportedChatInvite::REVOKED_MASK; + send_query(G()->net_query_creator().create(telegram_api::messages_editExportedChatInvite( + flags, false /*ignored*/, std::move(input_peer), invite_link, 0, 0))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + auto result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for RevokeChatInviteLinkQuery: " << to_string(result); + + vector> links; + switch (result->get_id()) { + case telegram_api::messages_exportedChatInvite::ID: { + auto invite = move_tl_object_as(result); + + td->contacts_manager_->on_get_users(std::move(invite->users_), "RevokeChatInviteLinkQuery"); + + DialogInviteLink invite_link(std::move(invite->invite_)); + if (!invite_link.is_valid()) { + return on_error(id, Status::Error(500, "Receive invalid invite link")); + } + links.push_back(invite_link.get_chat_invite_link_object(td->contacts_manager_.get())); + break; + } + case telegram_api::messages_exportedChatInviteReplaced::ID: { + auto invite = move_tl_object_as(result); + + td->contacts_manager_->on_get_users(std::move(invite->users_), "RevokeChatInviteLinkQuery"); + + DialogInviteLink invite_link(std::move(invite->invite_)); + DialogInviteLink new_invite_link(std::move(invite->new_invite_)); + if (!invite_link.is_valid() || !new_invite_link.is_valid()) { + return on_error(id, Status::Error(500, "Receive invalid invite link")); + } + if (new_invite_link.get_creator_user_id() == td->contacts_manager_->get_my_id() && + new_invite_link.is_permanent()) { + td->contacts_manager_->on_get_permanent_dialog_invite_link(dialog_id_, new_invite_link); + } + links.push_back(invite_link.get_chat_invite_link_object(td->contacts_manager_.get())); + links.push_back(new_invite_link.get_chat_invite_link_object(td->contacts_manager_.get())); + break; + } + default: + UNREACHABLE(); + } + auto total_count = static_cast(links.size()); + promise_.set_value(td_api::make_object(total_count, std::move(links))); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "RevokeChatInviteLinkQuery"); + promise_.set_error(std::move(status)); + } +}; + +class DeleteExportedChatInviteQuery : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit DeleteExportedChatInviteQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, const string &invite_link) { + dialog_id_ = dialog_id; + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + if (input_peer == nullptr) { + return on_error(0, Status::Error(400, "Can't access the chat")); + } + + send_query(G()->net_query_creator().create( + telegram_api::messages_deleteExportedChatInvite(std::move(input_peer), invite_link))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } - td->contacts_manager_->on_get_channel_invite_link(channel_id_, std::move(ptr)); promise_.set_value(Unit()); } void on_error(uint64 id, Status status) override { - td->contacts_manager_->on_get_channel_error(channel_id_, status, "ExportChannelInviteLinkQuery"); + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteExportedChatInviteQuery"); promise_.set_error(std::move(status)); - td->updates_manager_->get_difference("ExportChannelInviteLinkQuery"); } }; -class CheckDialogInviteLinkQuery : public Td::ResultHandler { +class DeleteRevokedExportedChatInvitesQuery : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit DeleteRevokedExportedChatInvitesQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, UserId creator_user_id) { + dialog_id_ = dialog_id; + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + if (input_peer == nullptr) { + return on_error(0, Status::Error(400, "Can't access the chat")); + } + + auto input_user = td->contacts_manager_->get_input_user(creator_user_id); + CHECK(input_user != nullptr); + + send_query(G()->net_query_creator().create( + telegram_api::messages_deleteRevokedExportedChatInvites(std::move(input_peer), std::move(input_user)))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + promise_.set_value(Unit()); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteRevokedExportedChatInvitesQuery"); + promise_.set_error(std::move(status)); + } +}; + +class CheckChatInviteQuery : public Td::ResultHandler { Promise promise_; string invite_link_; public: - explicit CheckDialogInviteLinkQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit CheckChatInviteQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &invite_link) { invite_link_ = invite_link; send_query(G()->net_query_creator().create( - telegram_api::messages_checkChatInvite(ContactsManager::get_dialog_invite_link_hash(invite_link_).str()))); + telegram_api::messages_checkChatInvite(DialogInviteLink::get_dialog_invite_link_hash(invite_link_).str()))); } void on_result(uint64 id, BufferSlice packet) override { @@ -1639,19 +2157,19 @@ class CheckDialogInviteLinkQuery : public Td::ResultHandler { } }; -class ImportDialogInviteLinkQuery : public Td::ResultHandler { +class ImportChatInviteQuery : public Td::ResultHandler { Promise promise_; string invite_link_; public: - explicit ImportDialogInviteLinkQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit ImportChatInviteQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &invite_link) { invite_link_ = invite_link; send_query(G()->net_query_creator().create( - telegram_api::messages_importChatInvite(ContactsManager::get_dialog_invite_link_hash(invite_link).str()))); + telegram_api::messages_importChatInvite(DialogInviteLink::get_dialog_invite_link_hash(invite_link).str()))); } void on_result(uint64 id, BufferSlice packet) override { @@ -1665,7 +2183,7 @@ class ImportDialogInviteLinkQuery : public Td::ResultHandler { auto dialog_ids = UpdatesManager::get_chat_dialog_ids(ptr.get()); if (dialog_ids.size() != 1u) { - LOG(ERROR) << "Receive wrong result for ImportDialogInviteLinkQuery: " << to_string(ptr); + LOG(ERROR) << "Receive wrong result for ImportChatInviteQuery: " << to_string(ptr); return on_error(id, Status::Error(500, "Internal Server Error")); } auto dialog_id = dialog_ids[0]; @@ -1690,9 +2208,13 @@ class DeleteChatUserQuery : public Td::ResultHandler { explicit DeleteChatUserQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(ChatId chat_id, tl_object_ptr &&input_user) { - send_query( - G()->net_query_creator().create(telegram_api::messages_deleteChatUser(chat_id.get(), std::move(input_user)))); + void send(ChatId chat_id, tl_object_ptr &&input_user, bool revoke_messages) { + int32 flags = 0; + if (revoke_messages) { + flags |= telegram_api::messages_deleteChatUser::REVOKE_HISTORY_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::messages_deleteChatUser(flags, false /*ignored*/, chat_id.get(), std::move(input_user)))); } void on_result(uint64 id, BufferSlice packet) override { @@ -2409,7 +2931,7 @@ class GetChannelParticipantQuery : public Td::ResultHandler { LOG(INFO) << "Receive result for GetChannelParticipantQuery: " << to_string(participant); td->contacts_manager_->on_get_users(std::move(participant->users_), "GetChannelParticipantQuery"); - auto result = td->contacts_manager_->get_dialog_participant(channel_id_, std::move(participant->participant_)); + DialogParticipant result(std::move(participant->participant_)); if (!result.is_valid()) { LOG(ERROR) << "Receive invalid " << result; return promise_.set_error(Status::Error(500, "Receive invalid chat member")); @@ -2429,30 +2951,23 @@ class GetChannelParticipantQuery : public Td::ResultHandler { }; class GetChannelParticipantsQuery : public Td::ResultHandler { - Promise promise_; + Promise> promise_; ChannelId channel_id_; - ChannelParticipantsFilter filter_{nullptr}; - int32 offset_; - int32 limit_; - int64 random_id_; public: - explicit GetChannelParticipantsQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit GetChannelParticipantsQuery(Promise> &&promise) + : promise_(std::move(promise)) { } - void send(ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset, int32 limit, int64 random_id) { + void send(ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset, int32 limit) { auto input_channel = td->contacts_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return promise_.set_error(Status::Error(3, "Supergroup not found")); } channel_id_ = channel_id; - filter_ = std::move(filter); - offset_ = offset; - limit_ = limit; - random_id_ = random_id; send_query(G()->net_query_creator().create(telegram_api::channels_getParticipants( - std::move(input_channel), filter_.get_input_channel_participants_filter(), offset, limit, 0))); + std::move(input_channel), filter.get_input_channel_participants_filter(), offset, limit, 0))); } void on_result(uint64 id, BufferSlice packet) override { @@ -2462,31 +2977,22 @@ class GetChannelParticipantsQuery : public Td::ResultHandler { } auto participants_ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetChannelParticipantsQuery with filter " - << to_string(filter_.get_input_channel_participants_filter()) << ": " << to_string(participants_ptr); + LOG(INFO) << "Receive result for GetChannelParticipantsQuery: " << to_string(participants_ptr); switch (participants_ptr->get_id()) { case telegram_api::channels_channelParticipants::ID: { - auto participants = telegram_api::move_object_as(participants_ptr); - td->contacts_manager_->on_get_users(std::move(participants->users_), "GetChannelParticipantsQuery"); - td->contacts_manager_->on_get_channel_participants_success(channel_id_, std::move(filter_), offset_, limit_, - random_id_, participants->count_, - std::move(participants->participants_)); + promise_.set_value(telegram_api::move_object_as(participants_ptr)); break; } case telegram_api::channels_channelParticipantsNotModified::ID: LOG(ERROR) << "Receive channelParticipantsNotModified"; - break; + return on_error(id, Status::Error(500, "Receive channelParticipantsNotModified")); default: UNREACHABLE(); } - - promise_.set_value(Unit()); } void on_error(uint64 id, Status status) override { td->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelParticipantsQuery"); - td->contacts_manager_->on_get_channel_participants_fail(channel_id_, std::move(filter_), offset_, limit_, - random_id_); promise_.set_error(std::move(status)); } }; @@ -2528,8 +3034,7 @@ class GetChannelAdministratorsQuery : public Td::ResultHandler { vector administrators; administrators.reserve(participants->participants_.size()); for (auto &participant : participants->participants_) { - DialogParticipant dialog_participant = - td->contacts_manager_->get_dialog_participant(channel_id_, std::move(participant)); + DialogParticipant dialog_participant(std::move(participant)); if (!dialog_participant.is_valid() || !dialog_participant.status.is_administrator()) { LOG(ERROR) << "Receive " << dialog_participant << " as an administrator of " << channel_id_; continue; @@ -2540,7 +3045,8 @@ class GetChannelAdministratorsQuery : public Td::ResultHandler { td->contacts_manager_->on_update_channel_administrator_count(channel_id_, narrow_cast(administrators.size())); - td->contacts_manager_->on_update_dialog_administrators(DialogId(channel_id_), std::move(administrators), true); + td->contacts_manager_->on_update_dialog_administrators(DialogId(channel_id_), std::move(administrators), true, + false); break; } @@ -2859,14 +3365,6 @@ class LoadAsyncGraphQuery : public Td::ResultHandler { } }; -bool ContactsManager::UserFull::is_expired() const { - return expires_at < Time::now(); -} - -bool ContactsManager::ChannelFull::is_expired() const { - return expires_at < Time::now(); -} - class ContactsManager::UploadProfilePhotoCallback : public FileManager::UploadCallback { public: void on_upload_ok(FileId file_id, tl_object_ptr input_file) override { @@ -2885,9 +3383,6 @@ class ContactsManager::UploadProfilePhotoCallback : public FileManager::UploadCa } }; -const CSlice ContactsManager::INVITE_LINK_URLS[3] = {"t.me/joinchat/", "telegram.me/joinchat/", - "telegram.dog/joinchat/"}; - ContactsManager::ContactsManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { upload_profile_photo_callback_ = std::make_shared(); @@ -2951,6 +3446,8 @@ ContactsManager::ContactsManager(Td *td, ActorShared<> parent) : td_(td), parent invite_link_info_expire_timeout_.set_callback_data(static_cast(this)); } +ContactsManager::~ContactsManager() = default; + void ContactsManager::tear_down() { parent_.reset(); // Completely clear memory when closing, to avoid memory leaks @@ -3172,6 +3669,7 @@ void ContactsManager::User::store(StorerT &storer) const { STORE_FLAG(is_mutual_contact); STORE_FLAG(has_restriction_reasons); STORE_FLAG(need_apply_min_photo); + STORE_FLAG(is_fake); END_STORE_FLAGS(); store(first_name, storer); if (has_last_name) { @@ -3241,6 +3739,7 @@ void ContactsManager::User::parse(ParserT &parser) { PARSE_FLAG(is_mutual_contact); PARSE_FLAG(has_restriction_reasons); PARSE_FLAG(need_apply_min_photo); + PARSE_FLAG(is_fake); END_PARSE_FLAGS(); parse(first_name, parser); if (has_last_name) { @@ -3475,13 +3974,15 @@ template void ContactsManager::ChatFull::store(StorerT &storer) const { using td::store; bool has_description = !description.empty(); - bool has_invite_link = !invite_link.empty(); + bool has_legacy_invite_link = false; bool has_photo = !photo.is_empty(); + bool has_invite_link = invite_link.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_description); - STORE_FLAG(has_invite_link); + STORE_FLAG(has_legacy_invite_link); STORE_FLAG(can_set_username); STORE_FLAG(has_photo); + STORE_FLAG(has_invite_link); END_STORE_FLAGS(); store(version, storer); store(creator_user_id, storer); @@ -3489,25 +3990,27 @@ void ContactsManager::ChatFull::store(StorerT &storer) const { if (has_description) { store(description, storer); } - if (has_invite_link) { - store(invite_link, storer); - } if (has_photo) { store(photo, storer); } + if (has_invite_link) { + store(invite_link, storer); + } } template void ContactsManager::ChatFull::parse(ParserT &parser) { using td::parse; bool has_description; - bool has_invite_link; + bool legacy_has_invite_link; bool has_photo; + bool has_invite_link; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_description); - PARSE_FLAG(has_invite_link); + PARSE_FLAG(legacy_has_invite_link); PARSE_FLAG(can_set_username); PARSE_FLAG(has_photo); + PARSE_FLAG(has_invite_link); END_PARSE_FLAGS(); parse(version, parser); parse(creator_user_id, parser); @@ -3515,12 +4018,16 @@ void ContactsManager::ChatFull::parse(ParserT &parser) { if (has_description) { parse(description, parser); } - if (has_invite_link) { - parse(invite_link, parser); + if (legacy_has_invite_link) { + string legacy_invite_link; + parse(legacy_invite_link, parser); } if (has_photo) { parse(photo, parser); } + if (has_invite_link) { + parse(invite_link, parser); + } } template @@ -3557,6 +4064,8 @@ void ContactsManager::Channel::store(StorerT &storer) const { STORE_FLAG(is_slow_mode_enabled); STORE_FLAG(has_restriction_reasons); STORE_FLAG(legacy_has_active_group_call); + STORE_FLAG(is_fake); + STORE_FLAG(is_gigagroup); END_STORE_FLAGS(); store(status, storer); @@ -3624,6 +4133,8 @@ void ContactsManager::Channel::parse(ParserT &parser) { PARSE_FLAG(is_slow_mode_enabled); PARSE_FLAG(has_restriction_reasons); PARSE_FLAG(legacy_has_active_group_call); + PARSE_FLAG(is_fake); + PARSE_FLAG(is_gigagroup); END_PARSE_FLAGS(); if (use_new_rights) { @@ -3683,7 +4194,7 @@ void ContactsManager::ChannelFull::store(StorerT &storer) const { bool has_administrator_count = administrator_count != 0; bool has_restricted_count = restricted_count != 0; bool has_banned_count = banned_count != 0; - bool has_invite_link = !invite_link.empty(); + bool legacy_has_invite_link = false; bool has_sticker_set = sticker_set_id.is_valid(); bool has_linked_channel_id = linked_channel_id.is_valid(); bool has_migrated_from_max_message_id = migrated_from_max_message_id.is_valid(); @@ -3695,12 +4206,13 @@ void ContactsManager::ChannelFull::store(StorerT &storer) const { bool has_stats_dc_id = stats_dc_id.is_exact(); bool has_photo = !photo.is_empty(); bool legacy_has_active_group_call_id = false; + bool has_invite_link = invite_link.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_description); STORE_FLAG(has_administrator_count); STORE_FLAG(has_restricted_count); STORE_FLAG(has_banned_count); - STORE_FLAG(has_invite_link); + STORE_FLAG(legacy_has_invite_link); STORE_FLAG(has_sticker_set); STORE_FLAG(has_linked_channel_id); STORE_FLAG(has_migrated_from_max_message_id); @@ -3720,6 +4232,7 @@ void ContactsManager::ChannelFull::store(StorerT &storer) const { STORE_FLAG(is_can_view_statistics_inited); STORE_FLAG(can_view_statistics); STORE_FLAG(legacy_has_active_group_call_id); + STORE_FLAG(has_invite_link); END_STORE_FLAGS(); if (has_description) { store(description, storer); @@ -3734,9 +4247,6 @@ void ContactsManager::ChannelFull::store(StorerT &storer) const { if (has_banned_count) { store(banned_count, storer); } - if (has_invite_link) { - store(invite_link, storer); - } if (has_sticker_set) { store(sticker_set_id, storer); } @@ -3768,6 +4278,9 @@ void ContactsManager::ChannelFull::store(StorerT &storer) const { if (has_photo) { store(photo, storer); } + if (has_invite_link) { + store(invite_link, storer); + } } template @@ -3777,7 +4290,7 @@ void ContactsManager::ChannelFull::parse(ParserT &parser) { bool has_administrator_count; bool has_restricted_count; bool has_banned_count; - bool has_invite_link; + bool legacy_has_invite_link; bool has_sticker_set; bool has_linked_channel_id; bool has_migrated_from_max_message_id; @@ -3790,12 +4303,13 @@ void ContactsManager::ChannelFull::parse(ParserT &parser) { bool has_stats_dc_id; bool has_photo; bool legacy_has_active_group_call_id; + bool has_invite_link; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_description); PARSE_FLAG(has_administrator_count); PARSE_FLAG(has_restricted_count); PARSE_FLAG(has_banned_count); - PARSE_FLAG(has_invite_link); + PARSE_FLAG(legacy_has_invite_link); PARSE_FLAG(has_sticker_set); PARSE_FLAG(has_linked_channel_id); PARSE_FLAG(has_migrated_from_max_message_id); @@ -3815,6 +4329,7 @@ void ContactsManager::ChannelFull::parse(ParserT &parser) { PARSE_FLAG(is_can_view_statistics_inited); PARSE_FLAG(can_view_statistics); PARSE_FLAG(legacy_has_active_group_call_id); + PARSE_FLAG(has_invite_link); END_PARSE_FLAGS(); if (has_description) { parse(description, parser); @@ -3829,8 +4344,9 @@ void ContactsManager::ChannelFull::parse(ParserT &parser) { if (has_banned_count) { parse(banned_count, parser); } - if (has_invite_link) { - parse(invite_link, parser); + if (legacy_has_invite_link) { + string legacy_invite_link; + parse(legacy_invite_link, parser); } if (has_sticker_set) { parse(sticker_set_id, parser); @@ -3867,6 +4383,9 @@ void ContactsManager::ChannelFull::parse(ParserT &parser) { InputGroupCallId input_group_call_id; parse(input_group_call_id, parser); } + if (has_invite_link) { + parse(invite_link, parser); + } if (legacy_can_view_statistics) { LOG(DEBUG) << "Ignore legacy can view statistics flag"; @@ -5760,8 +6279,24 @@ void ContactsManager::toggle_channel_is_all_history_available(ChannelId channel_ } // it can be toggled in public chats, but will not affect them - td_->create_handler(std::move(promise)) - ->send(channel_id, is_all_history_available); + td_->create_handler(std::move(promise))->send(channel_id, is_all_history_available); +} + +void ContactsManager::convert_channel_to_gigagroup(ChannelId channel_id, Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(6, "Supergroup not found")); + } + if (!get_channel_permissions(c).is_creator()) { + return promise.set_error(Status::Error(6, "Not enough rights to convert group to broadcast group")); + } + if (get_channel_type(c) != ChannelType::Megagroup) { + return promise.set_error(Status::Error(6, "Chat must be a supergroup")); + } + + remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); + + td_->create_handler(std::move(promise))->send(channel_id); } void ContactsManager::set_channel_description(ChannelId channel_id, const string &description, @@ -6110,18 +6645,54 @@ void ContactsManager::report_channel_spam(ChannelId channel_id, UserId user_id, td_->create_handler(std::move(promise))->send(channel_id, user_id, server_message_ids); } +void ContactsManager::delete_chat(ChatId chat_id, Promise &&promise) { + auto c = get_chat(chat_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat info not found")); + } + if (!get_chat_status(c).is_creator()) { + return promise.set_error(Status::Error(400, "Not enough rights to delete the chat")); + } + if (!c->is_active) { + return promise.set_error(Status::Error(400, "Chat is already deactivated")); + } + + td_->create_handler(std::move(promise))->send(chat_id); +} + void ContactsManager::delete_channel(ChannelId channel_id, Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { - return promise.set_error(Status::Error(6, "Supergroup not found")); + return promise.set_error(Status::Error(400, "Chat info not found")); } if (!get_channel_status(c).is_creator()) { - return promise.set_error(Status::Error(6, "Not enough rights to delete the supergroup")); + return promise.set_error(Status::Error(400, "Not enough rights to delete the chat")); } td_->create_handler(std::move(promise))->send(channel_id); } +void ContactsManager::delete_dialog(DialogId dialog_id, Promise &&promise) { + if (!td_->messages_manager_->have_dialog_force(dialog_id)) { + return promise.set_error(Status::Error(3, "Chat not found")); + } + + switch (dialog_id.get_type()) { + case DialogType::User: + return td_->messages_manager_->delete_dialog_history(dialog_id, true, true, std::move(promise)); + case DialogType::Chat: + return delete_chat(dialog_id.get_chat_id(), std::move(promise)); + case DialogType::Channel: + return delete_channel(dialog_id.get_channel_id(), std::move(promise)); + case DialogType::SecretChat: + send_closure(td_->secret_chats_manager_, &SecretChatsManager::cancel_chat, dialog_id.get_secret_chat_id(), true, + std::move(promise)); + return; + default: + UNREACHABLE(); + } +} + void ContactsManager::add_chat_participant(ChatId chat_id, UserId user_id, int32 forward_limit, Promise &&promise) { const Chat *c = get_chat(chat_id); @@ -6374,7 +6945,7 @@ void ContactsManager::promote_channel_participant(ChannelId channel_id, UserId u void ContactsManager::change_chat_participant_status(ChatId chat_id, UserId user_id, DialogParticipantStatus status, Promise &&promise) { if (!status.is_member()) { - return delete_chat_participant(chat_id, user_id, std::move(promise)); + return delete_chat_participant(chat_id, user_id, false, std::move(promise)); } auto c = get_chat(chat_id); @@ -6511,33 +7082,180 @@ void ContactsManager::transfer_channel_ownership( ->send(channel_id, user_id, std::move(input_check_password)); } -void ContactsManager::export_chat_invite_link(ChatId chat_id, Promise &&promise) { - const Chat *c = get_chat(chat_id); - if (c == nullptr) { - return promise.set_error(Status::Error(3, "Chat info not found")); - } - if (!c->is_active) { - return promise.set_error(Status::Error(3, "Chat is deactivated")); +Status ContactsManager::can_manage_dialog_invite_links(DialogId dialog_id, bool creator_only) { + if (!td_->messages_manager_->have_dialog_force(dialog_id)) { + return Status::Error(3, "Chat not found"); } - if (!get_chat_status(c).is_administrator() || !get_chat_status(c).can_invite_users()) { - return promise.set_error(Status::Error(3, "Not enough rights to export chat invite link")); + switch (dialog_id.get_type()) { + case DialogType::User: + return Status::Error(3, "Can't invite members to a private chat"); + case DialogType::Chat: { + const Chat *c = get_chat(dialog_id.get_chat_id()); + if (c == nullptr) { + return Status::Error(3, "Chat info not found"); + } + if (!c->is_active) { + return Status::Error(3, "Chat is deactivated"); + } + auto status = get_chat_status(c); + bool have_rights = creator_only ? status.is_creator() : status.is_administrator() && status.can_invite_users(); + if (!have_rights) { + return Status::Error(3, "Not enough rights to manage chat invite link"); + } + break; + } + case DialogType::Channel: { + const Channel *c = get_channel(dialog_id.get_channel_id()); + if (c == nullptr) { + return Status::Error(3, "Chat info not found"); + } + auto status = get_channel_status(c); + bool have_rights = creator_only ? status.is_creator() : status.is_administrator() && status.can_invite_users(); + if (!have_rights) { + return Status::Error(3, "Not enough rights to manage chat invite link"); + } + break; + } + case DialogType::SecretChat: + return Status::Error(3, "Can't invite members to a secret chat"); + case DialogType::None: + default: + UNREACHABLE(); } - - td_->create_handler(std::move(promise))->send(chat_id); + return Status::OK(); } -void ContactsManager::export_channel_invite_link(ChannelId channel_id, Promise &&promise) { - const Channel *c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(3, "Chat info not found")); +void ContactsManager::export_dialog_invite_link(DialogId dialog_id, int32 expire_date, int32 usage_limit, + bool is_permanent, + Promise> &&promise) { + get_me(PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, expire_date, usage_limit, is_permanent, + promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &ContactsManager::export_dialog_invite_link_impl, dialog_id, expire_date, usage_limit, + is_permanent, std::move(promise)); + } + })); +} + +void ContactsManager::export_dialog_invite_link_impl(DialogId dialog_id, int32 expire_date, int32 usage_limit, + bool is_permanent, + Promise> &&promise) { + if (G()->close_flag()) { + return promise.set_error(Status::Error(500, "Request aborted")); } - if (!get_channel_status(c).is_administrator() || !get_channel_status(c).can_invite_users()) { - return promise.set_error(Status::Error(3, "Not enough rights to export chat invite link")); + TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id)); + + td_->create_handler(std::move(promise)) + ->send(dialog_id, expire_date, usage_limit, is_permanent); +} + +void ContactsManager::edit_dialog_invite_link(DialogId dialog_id, const string &invite_link, int32 expire_date, + int32 usage_limit, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id)); + + if (invite_link.empty()) { + return promise.set_error(Status::Error(400, "Invite link must be non-empty")); } - td_->create_handler(std::move(promise))->send(channel_id); + td_->create_handler(std::move(promise)) + ->send(dialog_id, invite_link, expire_date, usage_limit); +} + +void ContactsManager::get_dialog_invite_link(DialogId dialog_id, const string &invite_link, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id, false)); + + if (invite_link.empty()) { + return promise.set_error(Status::Error(400, "Invite link must be non-empty")); + } + + td_->create_handler(std::move(promise))->send(dialog_id, invite_link); +} + +void ContactsManager::get_dialog_invite_link_counts( + DialogId dialog_id, Promise> &&promise) { + TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id, true)); + + td_->create_handler(std::move(promise))->send(dialog_id); +} + +void ContactsManager::get_dialog_invite_links(DialogId dialog_id, UserId creator_user_id, bool is_revoked, + int32 offset_date, const string &offset_invite_link, int32 limit, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id, creator_user_id != get_my_id())); + + if (!have_input_user(creator_user_id)) { + return promise.set_error(Status::Error(400, "Administrator user not found")); + } + + if (limit <= 0) { + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); + } + + td_->create_handler(std::move(promise)) + ->send(dialog_id, creator_user_id, is_revoked, offset_date, offset_invite_link, limit); +} + +void ContactsManager::get_dialog_invite_link_users( + DialogId dialog_id, const string &invite_link, td_api::object_ptr offset_member, + int32 limit, Promise> &&promise) { + TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id)); + + if (limit <= 0) { + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); + } + + if (invite_link.empty()) { + return promise.set_error(Status::Error(400, "Invite link must be non-empty")); + } + + UserId offset_user_id; + int32 offset_date = 0; + if (offset_member != nullptr) { + offset_user_id = UserId(offset_member->user_id_); + offset_date = offset_member->joined_chat_date_; + } + + td_->create_handler(std::move(promise)) + ->send(dialog_id, invite_link, offset_date, offset_user_id, limit); +} + +void ContactsManager::revoke_dialog_invite_link(DialogId dialog_id, const string &invite_link, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id)); + + if (invite_link.empty()) { + return promise.set_error(Status::Error(400, "Invite link must be non-empty")); + } + + td_->create_handler(std::move(promise))->send(dialog_id, invite_link); +} + +void ContactsManager::delete_revoked_dialog_invite_link(DialogId dialog_id, const string &invite_link, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id)); + + if (invite_link.empty()) { + return promise.set_error(Status::Error(400, "Invite link must be non-empty")); + } + + td_->create_handler(std::move(promise))->send(dialog_id, invite_link); +} + +void ContactsManager::delete_all_revoked_dialog_invite_links(DialogId dialog_id, UserId creator_user_id, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id, creator_user_id != get_my_id())); + + if (!have_input_user(creator_user_id)) { + return promise.set_error(Status::Error(400, "Administrator user not found")); + } + + td_->create_handler(std::move(promise))->send(dialog_id, creator_user_id); } void ContactsManager::check_dialog_invite_link(const string &invite_link, Promise &&promise) const { @@ -6545,41 +7263,23 @@ void ContactsManager::check_dialog_invite_link(const string &invite_link, Promis return promise.set_value(Unit()); } - if (!is_valid_invite_link(invite_link)) { - return promise.set_error(Status::Error(3, "Wrong invite link")); + if (!DialogInviteLink::is_valid_invite_link(invite_link)) { + return promise.set_error(Status::Error(400, "Wrong invite link")); } - td_->create_handler(std::move(promise))->send(invite_link); + td_->create_handler(std::move(promise))->send(invite_link); } void ContactsManager::import_dialog_invite_link(const string &invite_link, Promise &&promise) { - if (!is_valid_invite_link(invite_link)) { - return promise.set_error(Status::Error(3, "Wrong invite link")); + if (!DialogInviteLink::is_valid_invite_link(invite_link)) { + return promise.set_error(Status::Error(400, "Wrong invite link")); } - td_->create_handler(std::move(promise))->send(invite_link); + td_->create_handler(std::move(promise))->send(invite_link); } -string ContactsManager::get_chat_invite_link(ChatId chat_id) const { - auto chat_full = get_chat_full(chat_id); - if (chat_full == nullptr) { - auto it = dialog_invite_links_.find(DialogId(chat_id)); - return it == dialog_invite_links_.end() ? string() : it->second; - } - return chat_full->invite_link; -} - -string ContactsManager::get_channel_invite_link( - ChannelId channel_id) { // should be non-const to update ChannelFull cache - auto channel_full = get_channel_full(channel_id, "get_channel_invite_link"); - if (channel_full == nullptr) { - auto it = dialog_invite_links_.find(DialogId(channel_id)); - return it == dialog_invite_links_.end() ? string() : it->second; - } - return channel_full->invite_link; -} - -void ContactsManager::delete_chat_participant(ChatId chat_id, UserId user_id, Promise &&promise) { +void ContactsManager::delete_chat_participant(ChatId chat_id, UserId user_id, bool revoke_messages, + Promise &&promise) { const Chat *c = get_chat(chat_id); if (c == nullptr) { return promise.set_error(Status::Error(3, "Chat info not found")); @@ -6590,6 +7290,9 @@ void ContactsManager::delete_chat_participant(ChatId chat_id, UserId user_id, Pr auto my_id = get_my_id(); if (c->status.is_left()) { if (user_id == my_id) { + if (revoke_messages) { + return td_->messages_manager_->delete_dialog_history(DialogId(chat_id), true, true, std::move(promise)); + } return promise.set_value(Unit()); } else { return promise.set_error(Status::Error(3, "Not in the chat")); @@ -6628,7 +7331,7 @@ void ContactsManager::delete_chat_participant(ChatId chat_id, UserId user_id, Pr } // TODO invoke after - td_->create_handler(std::move(promise))->send(chat_id, std::move(input_user)); + td_->create_handler(std::move(promise))->send(chat_id, std::move(input_user), revoke_messages); } void ContactsManager::restrict_channel_participant(ChannelId channel_id, UserId user_id, DialogParticipantStatus status, @@ -6843,6 +7546,75 @@ void ContactsManager::remove_inactive_channel(ChannelId channel_id) { } } +void ContactsManager::remove_dialog_suggested_action(SuggestedAction action) { + auto it = dialog_suggested_actions_.find(action.dialog_id_); + if (it == dialog_suggested_actions_.end()) { + return; + } + remove_suggested_action(it->second, action); + if (it->second.empty()) { + dialog_suggested_actions_.erase(it); + } +} + +void ContactsManager::dismiss_suggested_action(SuggestedAction action, Promise &&promise) { + if (action.is_empty()) { + return promise.set_error(Status::Error(400, "Action must be non-empty")); + } + auto dialog_id = action.dialog_id_; + if (dialog_id == DialogId()) { + send_closure_later(G()->config_manager(), &ConfigManager::dismiss_suggested_action, std::move(action), + std::move(promise)); + return; + } + + if (!td_->messages_manager_->have_dialog(dialog_id)) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + return promise.set_error(Status::Error(400, "Can't access the chat")); + } + + auto it = dialog_suggested_actions_.find(dialog_id); + if (it == dialog_suggested_actions_.end() || !td::contains(it->second, action)) { + return promise.set_value(Unit()); + } + + auto action_str = action.get_suggested_action_str(); + if (action_str.empty()) { + return promise.set_value(Unit()); + } + + auto &queries = dismiss_suggested_action_queries_[dialog_id]; + queries.push_back(std::move(promise)); + if (queries.size() == 1) { + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), action](Result &&result) { + send_closure(actor_id, &ContactsManager::on_dismiss_suggested_action, action, std::move(result)); + }); + td_->create_handler(std::move(query_promise))->send(std::move(action)); + } +} + +void ContactsManager::on_dismiss_suggested_action(SuggestedAction action, Result &&result) { + auto it = dismiss_suggested_action_queries_.find(action.dialog_id_); + CHECK(it != dismiss_suggested_action_queries_.end()); + auto promises = std::move(it->second); + dismiss_suggested_action_queries_.erase(it); + + if (result.is_error()) { + for (auto &promise : promises) { + promise.set_error(result.error().clone()); + } + return; + } + + remove_dialog_suggested_action(action); + + for (auto &promise : promises) { + promise.set_value(Unit()); + } +} + void ContactsManager::on_imported_contacts(int64 random_id, vector imported_contact_user_ids, vector unimported_contact_invites) { LOG(INFO) << "Contacts import with random_id " << random_id @@ -7236,6 +8008,7 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, bool need_location_bot = (flags & USER_FLAG_NEED_LOCATION_BOT) != 0; bool has_bot_info_version = (flags & USER_FLAG_HAS_BOT_INFO_VERSION) != 0; bool need_apply_min_photo = (flags & USER_FLAG_NEED_APPLY_MIN_PHOTO) != 0; + bool is_fake = (flags & USER_FLAG_IS_FAKE) != 0; LOG_IF(ERROR, !is_support && expect_support) << "Receive non-support " << user_id << ", but expected a support user"; LOG_IF(ERROR, !can_join_groups && !is_bot) @@ -7266,8 +8039,9 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, int32 bot_info_version = has_bot_info_version ? user->bot_info_version_ : -1; if (is_verified != u->is_verified || is_support != u->is_support || is_bot != u->is_bot || can_join_groups != u->can_join_groups || can_read_all_group_messages != u->can_read_all_group_messages || - restriction_reasons != u->restriction_reasons || is_scam != u->is_scam || is_inline_bot != u->is_inline_bot || - inline_query_placeholder != u->inline_query_placeholder || need_location_bot != u->need_location_bot) { + restriction_reasons != u->restriction_reasons || is_scam != u->is_scam || is_fake != u->is_fake || + is_inline_bot != u->is_inline_bot || inline_query_placeholder != u->inline_query_placeholder || + need_location_bot != u->need_location_bot) { LOG_IF(ERROR, is_bot != u->is_bot && !is_deleted && !u->is_deleted && u->is_received) << "User.is_bot has changed for " << user_id << "/" << u->username << " from " << source << " from " << u->is_bot << " to " << is_bot; @@ -7278,6 +8052,7 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, u->can_read_all_group_messages = can_read_all_group_messages; u->restriction_reasons = std::move(restriction_reasons); u->is_scam = is_scam; + u->is_fake = is_fake; u->is_inline_bot = is_inline_bot; u->inline_query_placeholder = std::move(inline_query_placeholder); u->need_location_bot = need_location_bot; @@ -7621,9 +8396,9 @@ ContactsManager::User *ContactsManager::get_user_force(UserId user_id) { auto user = telegram_api::make_object( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, user_id.get(), 1, first_name, - string(), username, phone_number, std::move(profile_photo), nullptr, bot_info_version, Auto(), string(), - string()); + false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, user_id.get(), 1, + first_name, string(), username, phone_number, std::move(profile_photo), nullptr, bot_info_version, Auto(), + string(), string()); on_get_user(std::move(user), "get_user_force"); u = get_user(user_id); CHECK(u != nullptr && u->is_received); @@ -8604,6 +9379,7 @@ void ContactsManager::on_load_chat_full_from_database(ChatId chat_id, string val dependencies.user_ids.insert(participant.user_id); dependencies.user_ids.insert(participant.inviter_user_id); } + dependencies.user_ids.insert(chat_full->invite_link.get_creator_user_id()); resolve_dependencies_force(td_, dependencies, "chat_full"); for (auto &participant : chat_full->participants) { @@ -8612,6 +9388,14 @@ void ContactsManager::on_load_chat_full_from_database(ChatId chat_id, string val Chat *c = get_chat(chat_id); CHECK(c != nullptr); + + // ignore ChatFull without invite link + if (c->is_active && c->status.is_administrator() && c->status.can_invite_users() && + !chat_full->invite_link.is_valid()) { + chats_full_.erase(chat_id); + return; + } + if (td_->file_manager_->get_file_view(c->photo.small_file_id).get_unique_file_id() != td_->file_manager_->get_file_view(as_fake_dialog_photo(chat_full->photo).small_file_id).get_unique_file_id()) { chat_full->photo = Photo(); @@ -8691,6 +9475,7 @@ void ContactsManager::on_load_channel_full_from_database(ChannelId channel_id, s add_dialog_and_dependencies(dependencies, DialogId(channel_full->linked_channel_id)); dependencies.chat_ids.insert(channel_full->migrated_from_chat_id); dependencies.user_ids.insert(channel_full->bot_user_ids.begin(), channel_full->bot_user_ids.end()); + dependencies.user_ids.insert(channel_full->invite_link.get_creator_user_id()); resolve_dependencies_force(td_, dependencies, "channel_full"); for (auto &user_id : channel_full->bot_user_ids) { @@ -8699,6 +9484,13 @@ void ContactsManager::on_load_channel_full_from_database(ChannelId channel_id, s Channel *c = get_channel(channel_id); CHECK(c != nullptr); + + // ignore ChannelFull without invite link + if (c->status.is_administrator() && c->status.can_invite_users() && !channel_full->invite_link.is_valid()) { + channels_full_.erase(channel_id); + return; + } + if (td_->file_manager_->get_file_view(c->photo.small_file_id).get_unique_file_id() != td_->file_manager_->get_file_view(as_fake_dialog_photo(channel_full->photo).small_file_id).get_unique_file_id()) { channel_full->photo = Photo(); @@ -8721,6 +9513,8 @@ void ContactsManager::on_load_channel_full_from_database(ChannelId channel_id, s } } + td_->messages_manager_->on_dialog_bots_updated(DialogId(channel_id), channel_full->bot_user_ids, true); + update_channel_full(channel_full, channel_id, true); if (channel_full->expires_at == 0.0) { @@ -8766,7 +9560,7 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo } if (u->is_is_contact_changed) { td_->messages_manager_->on_dialog_user_is_contact_updated(DialogId(user_id), u->is_contact); - if (is_user_contact(u, user_id)) { + if (is_user_contact(u, user_id, false)) { auto user_full = get_user_full(user_id); if (user_full != nullptr && user_full->need_phone_number_privacy_exception) { on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, false); @@ -8964,6 +9758,10 @@ void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from } if (c->is_default_permissions_changed) { td_->messages_manager_->on_dialog_permissions_updated(DialogId(channel_id)); + if (c->default_permissions != + RestrictedRights(false, false, false, false, false, false, false, false, false, false, false)) { + remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); + } } if (!td_->auth_manager_->is_bot()) { if (c->restriction_reasons.empty()) { @@ -9002,7 +9800,8 @@ void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from bool have_read_access = have_input_peer_channel(c, channel_id, AccessRights::Read); bool is_member = c->status.is_member(); if (c->had_read_access && !have_read_access) { - send_closure_later(G()->messages_manager(), &MessagesManager::delete_dialog, DialogId(channel_id)); + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_deleted, DialogId(channel_id), + Promise()); } else if (!from_database && c->was_member != is_member) { DialogId dialog_id(channel_id); send_closure_later(G()->messages_manager(), &MessagesManager::force_create_dialog, dialog_id, "update channel", @@ -9040,6 +9839,11 @@ void ContactsManager::update_secret_chat(SecretChat *c, SecretChatId secret_chat c->state); c->is_state_changed = false; } + if (c->is_ttl_changed) { + send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_message_ttl_setting, + DialogId(secret_chat_id), MessageTtlSetting(c->ttl)); + c->is_ttl_changed = false; + } } if (c->is_changed) { send_closure(G()->td(), &Td::send_update, @@ -9100,8 +9904,9 @@ void ContactsManager::update_chat_full(ChatFull *chat_full, ChatId chat_id, bool bot_user_ids.push_back(user_id); } } - on_update_dialog_administrators(DialogId(chat_id), std::move(administrators), chat_full->version != -1); - td_->messages_manager_->on_dialog_bots_updated(DialogId(chat_id), std::move(bot_user_ids)); + on_update_dialog_administrators(DialogId(chat_id), std::move(administrators), chat_full->version != -1, + from_database); + td_->messages_manager_->on_dialog_bots_updated(DialogId(chat_id), std::move(bot_user_ids), from_database); { Chat *c = get_chat(chat_id); @@ -9182,91 +9987,96 @@ void ContactsManager::on_get_users(vector> &&u } } -void ContactsManager::on_get_user_full(tl_object_ptr &&user_full) { - UserId user_id = get_user_id(user_full->user_); +void ContactsManager::on_get_user_full(tl_object_ptr &&user) { + UserId user_id = get_user_id(user->user_); if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } - on_get_user(std::move(user_full->user_), "on_get_user_full"); + on_get_user(std::move(user->user_), "on_get_user_full"); User *u = get_user(user_id); if (u == nullptr) { return; } - td_->messages_manager_->on_update_dialog_notify_settings(DialogId(user_id), std::move(user_full->notify_settings_), + td_->messages_manager_->on_update_dialog_notify_settings(DialogId(user_id), std::move(user->notify_settings_), "on_get_user_full"); { MessageId pinned_message_id; - if ((user_full->flags_ & USER_FULL_FLAG_HAS_PINNED_MESSAGE) != 0) { - pinned_message_id = MessageId(ServerMessageId(user_full->pinned_msg_id_)); + if ((user->flags_ & USER_FULL_FLAG_HAS_PINNED_MESSAGE) != 0) { + pinned_message_id = MessageId(ServerMessageId(user->pinned_msg_id_)); } td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(user_id), pinned_message_id); } { FolderId folder_id; - if ((user_full->flags_ & USER_FULL_FLAG_HAS_FOLDER_ID) != 0) { - folder_id = FolderId(user_full->folder_id_); + if ((user->flags_ & USER_FULL_FLAG_HAS_FOLDER_ID) != 0) { + folder_id = FolderId(user->folder_id_); } td_->messages_manager_->on_update_dialog_folder_id(DialogId(user_id), folder_id); } td_->messages_manager_->on_update_dialog_has_scheduled_server_messages( - DialogId(user_id), (user_full->flags_ & USER_FULL_FLAG_HAS_SCHEDULED_MESSAGES) != 0); + DialogId(user_id), (user->flags_ & USER_FULL_FLAG_HAS_SCHEDULED_MESSAGES) != 0); + { + MessageTtlSetting message_ttl_setting; + if ((user->flags_ & USER_FULL_FLAG_HAS_MESSAGE_TTL) != 0) { + message_ttl_setting = MessageTtlSetting(user->ttl_period_); + } + td_->messages_manager_->on_update_dialog_message_ttl_setting(DialogId(user_id), message_ttl_setting); + } - UserFull *user = add_user_full(user_id); - user->expires_at = Time::now() + USER_FULL_EXPIRE_TIME; + UserFull *user_full = add_user_full(user_id); + user_full->expires_at = Time::now() + USER_FULL_EXPIRE_TIME; { - bool is_blocked = (user_full->flags_ & USER_FULL_FLAG_IS_BLOCKED) != 0; - on_update_user_full_is_blocked(user, user_id, is_blocked); + bool is_blocked = (user->flags_ & USER_FULL_FLAG_IS_BLOCKED) != 0; + on_update_user_full_is_blocked(user_full, user_id, is_blocked); td_->messages_manager_->on_update_dialog_is_blocked(DialogId(user_id), is_blocked); } - on_update_user_full_common_chat_count(user, user_id, user_full->common_chats_count_); + on_update_user_full_common_chat_count(user_full, user_id, user->common_chats_count_); on_update_user_full_need_phone_number_privacy_exception( - user, user_id, (user_full->settings_->flags_ & telegram_api::peerSettings::NEED_CONTACTS_EXCEPTION_MASK) != 0); + user_full, user_id, (user->settings_->flags_ & telegram_api::peerSettings::NEED_CONTACTS_EXCEPTION_MASK) != 0); - bool can_pin_messages = user_full->can_pin_message_; - if (user->can_pin_messages != can_pin_messages) { - user->can_pin_messages = can_pin_messages; - user->is_changed = true; + bool can_pin_messages = user->can_pin_message_; + if (user_full->can_pin_messages != can_pin_messages) { + user_full->can_pin_messages = can_pin_messages; + user_full->is_changed = true; } - bool can_be_called = user_full->phone_calls_available_ && !user_full->phone_calls_private_; - bool supports_video_calls = user_full->video_calls_available_ && !user_full->phone_calls_private_; - bool has_private_calls = user_full->phone_calls_private_; - if (user->can_be_called != can_be_called || user->supports_video_calls != supports_video_calls || - user->has_private_calls != has_private_calls || user->about != user_full->about_) { - user->can_be_called = can_be_called; - user->supports_video_calls = supports_video_calls; - user->has_private_calls = has_private_calls; - user->about = std::move(user_full->about_); + bool can_be_called = user->phone_calls_available_ && !user->phone_calls_private_; + bool supports_video_calls = user->video_calls_available_ && !user->phone_calls_private_; + bool has_private_calls = user->phone_calls_private_; + if (user_full->can_be_called != can_be_called || user_full->supports_video_calls != supports_video_calls || + user_full->has_private_calls != has_private_calls || user_full->about != user->about_) { + user_full->can_be_called = can_be_called; + user_full->supports_video_calls = supports_video_calls; + user_full->has_private_calls = has_private_calls; + user_full->about = std::move(user->about_); - user->is_changed = true; + user_full->is_changed = true; } - auto photo = get_photo(td_->file_manager_.get(), std::move(user_full->profile_photo_), DialogId(user_id)); - if (photo != user->photo) { - user->photo = std::move(photo); - user->is_changed = true; + auto photo = get_photo(td_->file_manager_.get(), std::move(user->profile_photo_), DialogId(user_id)); + if (photo != user_full->photo) { + user_full->photo = std::move(photo); + user_full->is_changed = true; } - if (user->photo.is_empty()) { + if (user_full->photo.is_empty()) { drop_user_photos(user_id, true, false, "on_get_user_full"); } else { - register_user_photo(u, user_id, user->photo); + register_user_photo(u, user_id, user_full->photo); } - if (user_full->bot_info_ != nullptr) { - if (on_update_bot_info(std::move(user_full->bot_info_), false)) { - user->need_send_update = true; - } + if (on_update_bot_info(std::move(user->bot_info_), false)) { + user_full->need_send_update = true; } - update_user_full(user, user_id); + update_user_full(user_full, user_id); // update peer settings after UserFull is created and updated to not update twice need_phone_number_privacy_exception - td_->messages_manager_->on_get_peer_settings(DialogId(user_id), std::move(user_full->settings_)); + td_->messages_manager_->on_get_peer_settings(DialogId(user_id), std::move(user->settings_)); } void ContactsManager::on_get_user_photos(UserId user_id, int32 offset, int32 limit, int32 total_count, @@ -9355,7 +10165,10 @@ void ContactsManager::on_get_user_photos(UserId user_id, int32 offset, int32 lim } bool ContactsManager::on_update_bot_info(tl_object_ptr &&new_bot_info, bool send_update) { - CHECK(new_bot_info != nullptr); + if (new_bot_info == nullptr) { + return false; + } + UserId user_id(new_bot_info->user_id_); if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; @@ -9428,8 +10241,8 @@ void ContactsManager::on_get_chats(vector> &&c void ContactsManager::on_get_chat_full(tl_object_ptr &&chat_full_ptr, Promise &&promise) { LOG(INFO) << "Receive " << to_string(chat_full_ptr); if (chat_full_ptr->get_id() == telegram_api::chatFull::ID) { - auto chat_full = move_tl_object_as(chat_full_ptr); - ChatId chat_id(chat_full->id_); + auto chat = move_tl_object_as(chat_full_ptr); + ChatId chat_id(chat->id_); if (!chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id; return promise.set_value(Unit()); @@ -9437,8 +10250,8 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c { MessageId pinned_message_id; - if ((chat_full->flags_ & CHAT_FULL_FLAG_HAS_PINNED_MESSAGE) != 0) { - pinned_message_id = MessageId(ServerMessageId(chat_full->pinned_msg_id_)); + if ((chat->flags_ & CHAT_FULL_FLAG_HAS_PINNED_MESSAGE) != 0) { + pinned_message_id = MessageId(ServerMessageId(chat->pinned_msg_id_)); } Chat *c = get_chat(chat_id); if (c == nullptr) { @@ -9457,75 +10270,90 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c } { FolderId folder_id; - if ((chat_full->flags_ & CHAT_FULL_FLAG_HAS_FOLDER_ID) != 0) { - folder_id = FolderId(chat_full->folder_id_); + if ((chat->flags_ & CHAT_FULL_FLAG_HAS_FOLDER_ID) != 0) { + folder_id = FolderId(chat->folder_id_); } td_->messages_manager_->on_update_dialog_folder_id(DialogId(chat_id), folder_id); } td_->messages_manager_->on_update_dialog_has_scheduled_server_messages( - DialogId(chat_id), (chat_full->flags_ & CHAT_FULL_FLAG_HAS_SCHEDULED_MESSAGES) != 0); + DialogId(chat_id), (chat->flags_ & CHAT_FULL_FLAG_HAS_SCHEDULED_MESSAGES) != 0); { InputGroupCallId input_group_call_id; - if (chat_full->call_ != nullptr) { - input_group_call_id = InputGroupCallId(chat_full->call_); + if (chat->call_ != nullptr) { + input_group_call_id = InputGroupCallId(chat->call_); } td_->messages_manager_->on_update_dialog_group_call_id(DialogId(chat_id), input_group_call_id); } + { + MessageTtlSetting message_ttl_setting; + if ((chat->flags_ & CHAT_FULL_FLAG_HAS_MESSAGE_TTL) != 0) { + message_ttl_setting = MessageTtlSetting(chat->ttl_period_); + } + td_->messages_manager_->on_update_dialog_message_ttl_setting(DialogId(chat_id), message_ttl_setting); + } - ChatFull *chat = add_chat_full(chat_id); - on_update_chat_full_invite_link(chat, std::move(chat_full->exported_invite_)); - on_update_chat_full_photo( - chat, chat_id, get_photo(td_->file_manager_.get(), std::move(chat_full->chat_photo_), DialogId(chat_id))); + ChatFull *chat_full = add_chat_full(chat_id); + on_update_chat_full_invite_link(chat_full, std::move(chat->exported_invite_)); + on_update_chat_full_photo(chat_full, chat_id, + get_photo(td_->file_manager_.get(), std::move(chat->chat_photo_), DialogId(chat_id))); - for (auto &bot_info : chat_full->bot_info_) { + for (auto &bot_info : chat->bot_info_) { if (on_update_bot_info(std::move(bot_info))) { - chat->need_send_update = true; + chat_full->need_send_update = true; } } - if (chat->description != chat_full->about_) { - chat->description = std::move(chat_full->about_); - chat->is_changed = true; + if (chat_full->description != chat->about_) { + chat_full->description = std::move(chat->about_); + chat_full->is_changed = true; } - if (chat->can_set_username != chat_full->can_set_username_) { - chat->can_set_username = chat_full->can_set_username_; - chat->is_changed = true; + if (chat_full->can_set_username != chat->can_set_username_) { + chat_full->can_set_username = chat->can_set_username_; + chat_full->is_changed = true; } - on_get_chat_participants(std::move(chat_full->participants_), false); - td_->messages_manager_->on_update_dialog_notify_settings(DialogId(chat_id), std::move(chat_full->notify_settings_), + on_get_chat_participants(std::move(chat->participants_), false); + td_->messages_manager_->on_update_dialog_notify_settings(DialogId(chat_id), std::move(chat->notify_settings_), "on_get_chat_full"); - update_chat_full(chat, chat_id); + update_chat_full(chat_full, chat_id); } else { CHECK(chat_full_ptr->get_id() == telegram_api::channelFull::ID); - auto channel_full = move_tl_object_as(chat_full_ptr); - ChannelId channel_id(channel_full->id_); + auto channel = move_tl_object_as(chat_full_ptr); + ChannelId channel_id(channel->id_); if (!channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id; return promise.set_value(Unit()); } if (!G()->close_flag()) { - auto channel = get_channel_full(channel_id, "on_get_channel_full"); - if (channel != nullptr) { - if (channel->repair_request_version != 0 && channel->repair_request_version < channel->speculative_version) { - LOG(INFO) << "Receive ChannelFull with request version " << channel->repair_request_version - << ", but current speculative version is " << channel->speculative_version; + auto channel_full = get_channel_full(channel_id, "on_get_channel_full"); + if (channel_full != nullptr) { + if (channel_full->repair_request_version != 0 && + channel_full->repair_request_version < channel_full->speculative_version) { + LOG(INFO) << "Receive ChannelFull with request version " << channel_full->repair_request_version + << ", but current speculative version is " << channel_full->speculative_version; - channel->repair_request_version = channel->speculative_version; + channel_full->repair_request_version = channel_full->speculative_version; auto input_channel = get_input_channel(channel_id); CHECK(input_channel != nullptr); td_->create_handler(std::move(promise))->send(channel_id, std::move(input_channel)); return; } - channel->repair_request_version = 0; + channel_full->repair_request_version = 0; } } - td_->messages_manager_->on_update_dialog_notify_settings( - DialogId(channel_id), std::move(channel_full->notify_settings_), "on_get_channel_full"); + td_->messages_manager_->on_update_dialog_notify_settings(DialogId(channel_id), std::move(channel->notify_settings_), + "on_get_channel_full"); + { + MessageTtlSetting message_ttl_setting; + if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_MESSAGE_TTL) != 0) { + message_ttl_setting = MessageTtlSetting(channel->ttl_period_); + } + td_->messages_manager_->on_update_dialog_message_ttl_setting(DialogId(channel_id), message_ttl_setting); + } auto c = get_channel(channel_id); if (c == nullptr) { @@ -9533,119 +10361,119 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c return promise.set_value(Unit()); } - ChannelFull *channel = add_channel_full(channel_id); + ChannelFull *channel_full = add_channel_full(channel_id); - bool have_participant_count = (channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_PARTICIPANT_COUNT) != 0; - auto participant_count = have_participant_count ? channel_full->participants_count_ : channel->participant_count; + bool have_participant_count = (channel->flags_ & CHANNEL_FULL_FLAG_HAS_PARTICIPANT_COUNT) != 0; + auto participant_count = have_participant_count ? channel->participants_count_ : channel_full->participant_count; auto administrator_count = 0; - if ((channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_ADMINISTRATOR_COUNT) != 0) { - administrator_count = channel_full->admins_count_; + if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_ADMINISTRATOR_COUNT) != 0) { + administrator_count = channel->admins_count_; } else if (c->is_megagroup || c->status.is_administrator()) { // in megagroups and administered channels don't drop known number of administrators - administrator_count = channel->administrator_count; + administrator_count = channel_full->administrator_count; } if (participant_count < administrator_count) { participant_count = administrator_count; } - auto restricted_count = - (channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_BANNED_COUNT) != 0 ? channel_full->banned_count_ : 0; - auto banned_count = - (channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_BANNED_COUNT) != 0 ? channel_full->kicked_count_ : 0; - auto can_get_participants = (channel_full->flags_ & CHANNEL_FULL_FLAG_CAN_GET_PARTICIPANTS) != 0; - auto can_set_username = (channel_full->flags_ & CHANNEL_FULL_FLAG_CAN_SET_USERNAME) != 0; - auto can_set_sticker_set = (channel_full->flags_ & CHANNEL_FULL_FLAG_CAN_SET_STICKER_SET) != 0; - auto can_set_location = (channel_full->flags_ & CHANNEL_FULL_FLAG_CAN_SET_LOCATION) != 0; - auto is_all_history_available = (channel_full->flags_ & CHANNEL_FULL_FLAG_IS_ALL_HISTORY_HIDDEN) == 0; - auto can_view_statistics = (channel_full->flags_ & CHANNEL_FULL_FLAG_CAN_VIEW_STATISTICS) != 0; + auto restricted_count = (channel->flags_ & CHANNEL_FULL_FLAG_HAS_BANNED_COUNT) != 0 ? channel->banned_count_ : 0; + auto banned_count = (channel->flags_ & CHANNEL_FULL_FLAG_HAS_BANNED_COUNT) != 0 ? channel->kicked_count_ : 0; + auto can_get_participants = (channel->flags_ & CHANNEL_FULL_FLAG_CAN_GET_PARTICIPANTS) != 0; + auto can_set_username = (channel->flags_ & CHANNEL_FULL_FLAG_CAN_SET_USERNAME) != 0; + auto can_set_sticker_set = (channel->flags_ & CHANNEL_FULL_FLAG_CAN_SET_STICKER_SET) != 0; + auto can_set_location = (channel->flags_ & CHANNEL_FULL_FLAG_CAN_SET_LOCATION) != 0; + auto is_all_history_available = (channel->flags_ & CHANNEL_FULL_FLAG_IS_ALL_HISTORY_HIDDEN) == 0; + auto can_view_statistics = (channel->flags_ & CHANNEL_FULL_FLAG_CAN_VIEW_STATISTICS) != 0; StickerSetId sticker_set_id; - if (channel_full->stickerset_ != nullptr) { + if (channel->stickerset_ != nullptr) { sticker_set_id = - td_->stickers_manager_->on_get_sticker_set(std::move(channel_full->stickerset_), true, "on_get_channel_full"); + td_->stickers_manager_->on_get_sticker_set(std::move(channel->stickerset_), true, "on_get_channel_full"); } DcId stats_dc_id; - if ((channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_STATISTICS_DC_ID) != 0) { - stats_dc_id = DcId::create(channel_full->stats_dc_); + if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_STATISTICS_DC_ID) != 0) { + stats_dc_id = DcId::create(channel->stats_dc_); } if (!stats_dc_id.is_exact() && can_view_statistics) { LOG(ERROR) << "Receive can_view_statistics == true, but invalid statistics DC ID in " << channel_id; can_view_statistics = false; } - channel->repair_request_version = 0; - channel->expires_at = Time::now() + CHANNEL_FULL_EXPIRE_TIME; - if (channel->description != channel_full->about_ || channel->participant_count != participant_count || - channel->administrator_count != administrator_count || channel->restricted_count != restricted_count || - channel->banned_count != banned_count || channel->can_get_participants != can_get_participants || - channel->can_set_username != can_set_username || channel->can_set_sticker_set != can_set_sticker_set || - channel->can_set_location != can_set_location || channel->can_view_statistics != can_view_statistics || - channel->stats_dc_id != stats_dc_id || channel->sticker_set_id != sticker_set_id || - channel->is_all_history_available != is_all_history_available) { - channel->description = std::move(channel_full->about_); - channel->participant_count = participant_count; - channel->administrator_count = administrator_count; - channel->restricted_count = restricted_count; - channel->banned_count = banned_count; - channel->can_get_participants = can_get_participants; - channel->can_set_username = can_set_username; - channel->can_set_sticker_set = can_set_sticker_set; - channel->can_set_location = can_set_location; - channel->can_view_statistics = can_view_statistics; - channel->stats_dc_id = stats_dc_id; - channel->is_all_history_available = is_all_history_available; - channel->sticker_set_id = sticker_set_id; + channel_full->repair_request_version = 0; + channel_full->expires_at = Time::now() + CHANNEL_FULL_EXPIRE_TIME; + if (channel_full->description != channel->about_ || channel_full->participant_count != participant_count || + channel_full->administrator_count != administrator_count || + channel_full->restricted_count != restricted_count || channel_full->banned_count != banned_count || + channel_full->can_get_participants != can_get_participants || + channel_full->can_set_username != can_set_username || + channel_full->can_set_sticker_set != can_set_sticker_set || + channel_full->can_set_location != can_set_location || + channel_full->can_view_statistics != can_view_statistics || channel_full->stats_dc_id != stats_dc_id || + channel_full->sticker_set_id != sticker_set_id || + channel_full->is_all_history_available != is_all_history_available) { + channel_full->description = std::move(channel->about_); + channel_full->participant_count = participant_count; + channel_full->administrator_count = administrator_count; + channel_full->restricted_count = restricted_count; + channel_full->banned_count = banned_count; + channel_full->can_get_participants = can_get_participants; + channel_full->can_set_username = can_set_username; + channel_full->can_set_sticker_set = can_set_sticker_set; + channel_full->can_set_location = can_set_location; + channel_full->can_view_statistics = can_view_statistics; + channel_full->stats_dc_id = stats_dc_id; + channel_full->is_all_history_available = is_all_history_available; + channel_full->sticker_set_id = sticker_set_id; - channel->is_changed = true; + channel_full->is_changed = true; } if (have_participant_count && c->participant_count != participant_count) { c->participant_count = participant_count; c->is_changed = true; update_channel(c, channel_id); } - if (!channel->is_can_view_statistics_inited) { - channel->is_can_view_statistics_inited = true; - channel->need_save_to_database = true; + if (!channel_full->is_can_view_statistics_inited) { + channel_full->is_can_view_statistics_inited = true; + channel_full->need_save_to_database = true; } on_update_channel_full_photo( - channel, channel_id, - get_photo(td_->file_manager_.get(), std::move(channel_full->chat_photo_), DialogId(channel_id))); + channel_full, channel_id, + get_photo(td_->file_manager_.get(), std::move(channel->chat_photo_), DialogId(channel_id))); td_->messages_manager_->on_read_channel_outbox(channel_id, - MessageId(ServerMessageId(channel_full->read_outbox_max_id_))); - if ((channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_AVAILABLE_MIN_MESSAGE_ID) != 0) { + MessageId(ServerMessageId(channel->read_outbox_max_id_))); + if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_AVAILABLE_MIN_MESSAGE_ID) != 0) { td_->messages_manager_->on_update_channel_max_unavailable_message_id( - channel_id, MessageId(ServerMessageId(channel_full->available_min_id_))); + channel_id, MessageId(ServerMessageId(channel->available_min_id_))); } - td_->messages_manager_->on_read_channel_inbox(channel_id, - MessageId(ServerMessageId(channel_full->read_inbox_max_id_)), - channel_full->unread_count_, channel_full->pts_, "ChannelFull"); + td_->messages_manager_->on_read_channel_inbox(channel_id, MessageId(ServerMessageId(channel->read_inbox_max_id_)), + channel->unread_count_, channel->pts_, "ChannelFull"); - on_update_channel_full_invite_link(channel, std::move(channel_full->exported_invite_)); + on_update_channel_full_invite_link(channel_full, std::move(channel->exported_invite_)); { - auto is_blocked = (channel_full->flags_ & CHANNEL_FULL_FLAG_IS_BLOCKED) != 0; + auto is_blocked = (channel->flags_ & CHANNEL_FULL_FLAG_IS_BLOCKED) != 0; td_->messages_manager_->on_update_dialog_is_blocked(DialogId(channel_id), is_blocked); } { MessageId pinned_message_id; - if ((channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_PINNED_MESSAGE) != 0) { - pinned_message_id = MessageId(ServerMessageId(channel_full->pinned_msg_id_)); + if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_PINNED_MESSAGE) != 0) { + pinned_message_id = MessageId(ServerMessageId(channel->pinned_msg_id_)); } td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(channel_id), pinned_message_id); } { FolderId folder_id; - if ((channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_FOLDER_ID) != 0) { - folder_id = FolderId(channel_full->folder_id_); + if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_FOLDER_ID) != 0) { + folder_id = FolderId(channel->folder_id_); } td_->messages_manager_->on_update_dialog_folder_id(DialogId(channel_id), folder_id); } td_->messages_manager_->on_update_dialog_has_scheduled_server_messages( - DialogId(channel_id), (channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_SCHEDULED_MESSAGES) != 0); + DialogId(channel_id), (channel->flags_ & CHANNEL_FULL_FLAG_HAS_SCHEDULED_MESSAGES) != 0); { InputGroupCallId input_group_call_id; - if (channel_full->call_ != nullptr) { - input_group_call_id = InputGroupCallId(channel_full->call_); + if (channel->call_ != nullptr) { + input_group_call_id = InputGroupCallId(channel->call_); if (input_group_call_id.is_valid() && !c->is_megagroup) { LOG(ERROR) << "Receive " << input_group_call_id << " in " << channel_id; input_group_call_id = InputGroupCallId(); @@ -9656,14 +10484,14 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c if (participant_count >= 190) { int32 online_member_count = 0; - if ((channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_ONLINE_MEMBER_COUNT) != 0) { - online_member_count = channel_full->online_count_; + if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_ONLINE_MEMBER_COUNT) != 0) { + online_member_count = channel->online_count_; } td_->messages_manager_->on_update_dialog_online_member_count(DialogId(channel_id), online_member_count, true); } vector bot_user_ids; - for (auto &bot_info : channel_full->bot_info_) { + for (auto &bot_info : channel->bot_info_) { UserId user_id(bot_info->user_id_); if (!is_user_bot(user_id)) { continue; @@ -9672,11 +10500,11 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c bot_user_ids.push_back(user_id); on_update_bot_info(std::move(bot_info)); } - on_update_channel_full_bot_user_ids(channel, channel_id, std::move(bot_user_ids)); + on_update_channel_full_bot_user_ids(channel_full, channel_id, std::move(bot_user_ids)); ChannelId linked_channel_id; - if ((channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_LINKED_CHANNEL_ID) != 0) { - linked_channel_id = ChannelId(channel_full->linked_chat_id_); + if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_LINKED_CHANNEL_ID) != 0) { + linked_channel_id = ChannelId(channel->linked_chat_id_); auto linked_channel = get_channel_force(linked_channel_id); if (linked_channel == nullptr || c->is_megagroup == linked_channel->is_megagroup || channel_id == linked_channel_id) { @@ -9684,38 +10512,38 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c linked_channel_id = ChannelId(); } } - on_update_channel_full_linked_channel_id(channel, channel_id, linked_channel_id); + on_update_channel_full_linked_channel_id(channel_full, channel_id, linked_channel_id); - on_update_channel_full_location(channel, channel_id, DialogLocation(std::move(channel_full->location_))); + on_update_channel_full_location(channel_full, channel_id, DialogLocation(std::move(channel->location_))); if (c->is_megagroup) { int32 slow_mode_delay = 0; int32 slow_mode_next_send_date = 0; - if ((channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_SLOW_MODE_DELAY) != 0) { - slow_mode_delay = channel_full->slowmode_seconds_; + if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_SLOW_MODE_DELAY) != 0) { + slow_mode_delay = channel->slowmode_seconds_; } - if ((channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_SLOW_MODE_NEXT_SEND_DATE) != 0) { - slow_mode_next_send_date = channel_full->slowmode_next_send_date_; + if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_SLOW_MODE_NEXT_SEND_DATE) != 0) { + slow_mode_next_send_date = channel->slowmode_next_send_date_; } - on_update_channel_full_slow_mode_delay(channel, channel_id, slow_mode_delay, slow_mode_next_send_date); + on_update_channel_full_slow_mode_delay(channel_full, channel_id, slow_mode_delay, slow_mode_next_send_date); } ChatId migrated_from_chat_id; MessageId migrated_from_max_message_id; - if ((channel_full->flags_ & CHANNEL_FULL_FLAG_MIGRATED_FROM) != 0) { - migrated_from_chat_id = ChatId(channel_full->migrated_from_chat_id_); - migrated_from_max_message_id = MessageId(ServerMessageId(channel_full->migrated_from_max_id_)); + if ((channel->flags_ & CHANNEL_FULL_FLAG_MIGRATED_FROM) != 0) { + migrated_from_chat_id = ChatId(channel->migrated_from_chat_id_); + migrated_from_max_message_id = MessageId(ServerMessageId(channel->migrated_from_max_id_)); } - if (channel->migrated_from_chat_id != migrated_from_chat_id || - channel->migrated_from_max_message_id != migrated_from_max_message_id) { - channel->migrated_from_chat_id = migrated_from_chat_id; - channel->migrated_from_max_message_id = migrated_from_max_message_id; - channel->is_changed = true; + if (channel_full->migrated_from_chat_id != migrated_from_chat_id || + channel_full->migrated_from_max_message_id != migrated_from_max_message_id) { + channel_full->migrated_from_chat_id = migrated_from_chat_id; + channel_full->migrated_from_max_message_id = migrated_from_max_message_id; + channel_full->is_changed = true; } - update_channel_full(channel, channel_id); + update_channel_full(channel_full, channel_id); if (linked_channel_id.is_valid()) { auto linked_channel_full = get_channel_full_force(linked_channel_id, "on_get_chat_full"); @@ -9724,6 +10552,32 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c update_channel_full(linked_channel_full, linked_channel_id); } } + + if (dismiss_suggested_action_queries_.count(DialogId(channel_id)) == 0) { + auto it = dialog_suggested_actions_.find(DialogId(channel_id)); + if (it != dialog_suggested_actions_.end() || !channel->pending_suggestions_.empty()) { + vector suggested_actions; + for (auto &action_str : channel->pending_suggestions_) { + SuggestedAction suggested_action(action_str, DialogId(channel_id)); + if (!suggested_action.is_empty()) { + if (suggested_action == SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)} && + (c->is_gigagroup || c->default_permissions != RestrictedRights(false, false, false, false, false, false, + false, false, false, false, false))) { + LOG(INFO) << "Skip ConvertToGigagroup suggested action"; + } else { + suggested_actions.push_back(suggested_action); + } + } + } + if (it == dialog_suggested_actions_.end()) { + it = dialog_suggested_actions_.emplace(DialogId(channel_id), vector()).first; + } + update_suggested_actions(it->second, std::move(suggested_actions)); + if (it->second.empty()) { + dialog_suggested_actions_.erase(it); + } + } + } } promise.set_value(Unit()); } @@ -10473,29 +11327,10 @@ void ContactsManager::on_get_chat_participants(tl_object_ptrparticipants_.size()); for (auto &participant_ptr : participants->participants_) { - DialogParticipant dialog_participant; - switch (participant_ptr->get_id()) { - case telegram_api::chatParticipant::ID: { - auto participant = move_tl_object_as(participant_ptr); - dialog_participant = {UserId(participant->user_id_), UserId(participant->inviter_id_), participant->date_, - DialogParticipantStatus::Member()}; - break; - } - case telegram_api::chatParticipantCreator::ID: { - auto participant = move_tl_object_as(participant_ptr); - new_creator_user_id = UserId(participant->user_id_); - dialog_participant = {new_creator_user_id, new_creator_user_id, c->date, - DialogParticipantStatus::Creator(true, false, string())}; - break; - } - case telegram_api::chatParticipantAdmin::ID: { - auto participant = move_tl_object_as(participant_ptr); - dialog_participant = {UserId(participant->user_id_), UserId(participant->inviter_id_), participant->date_, - DialogParticipantStatus::GroupAdministrator(c->status.is_creator())}; - break; - } - default: - UNREACHABLE(); + DialogParticipant dialog_participant(std::move(participant_ptr), c->date, c->status.is_creator()); + if (!dialog_participant.is_valid()) { + LOG(ERROR) << "Receive invalid " << dialog_participant; + continue; } LOG_IF(ERROR, !have_user(dialog_participant.user_id)) @@ -10508,18 +11343,17 @@ void ContactsManager::on_get_chat_participants(tl_object_ptrdate; dialog_participant.joined_date = c->date; } + if (dialog_participant.status.is_creator()) { + new_creator_user_id = dialog_participant.user_id; + } new_participants.push_back(std::move(dialog_participant)); } - if (new_creator_user_id.is_valid()) { - LOG_IF(ERROR, !have_user(new_creator_user_id)) - << "Have no information about group creator " << new_creator_user_id << " in " << chat_id; - if (chat_full->creator_user_id.is_valid() && chat_full->creator_user_id != new_creator_user_id) { + if (chat_full->creator_user_id != new_creator_user_id) { + if (new_creator_user_id.is_valid() && chat_full->creator_user_id.is_valid()) { LOG(ERROR) << "Group creator has changed from " << chat_full->creator_user_id << " to " << new_creator_user_id << " in " << chat_id; } - } - if (chat_full->creator_user_id != new_creator_user_id) { chat_full->creator_user_id = new_creator_user_id; chat_full->is_changed = true; } @@ -10539,10 +11373,10 @@ const DialogParticipant *ContactsManager::get_chat_participant(ChatId chat_id, U if (chat_full == nullptr) { return nullptr; } - return get_chat_participant(chat_full, user_id); + return get_chat_full_participant(chat_full, user_id); } -const DialogParticipant *ContactsManager::get_chat_participant(const ChatFull *chat_full, UserId user_id) { +const DialogParticipant *ContactsManager::get_chat_full_participant(const ChatFull *chat_full, UserId user_id) { for (const auto &dialog_participant : chat_full->participants) { if (dialog_participant.user_id == user_id) { return &dialog_participant; @@ -10551,11 +11385,6 @@ const DialogParticipant *ContactsManager::get_chat_participant(const ChatFull *c return nullptr; } -DialogParticipant ContactsManager::get_dialog_participant( - ChannelId channel_id, tl_object_ptr &&participant_ptr) const { - return DialogParticipant(std::move(participant_ptr), get_channel_status(channel_id)); -} - tl_object_ptr ContactsManager::get_chat_member_object( const DialogParticipant &dialog_participant) const { UserId participant_user_id = dialog_participant.user_id; @@ -10631,17 +11460,25 @@ bool ContactsManager::on_get_channel_error(ChannelId channel_id, const Status &s return false; } -bool ContactsManager::is_user_contact(UserId user_id) const { - return is_user_contact(get_user(user_id), user_id); +bool ContactsManager::is_user_contact(UserId user_id, bool is_mutual) const { + return is_user_contact(get_user(user_id), user_id, is_mutual); } -bool ContactsManager::is_user_contact(const User *u, UserId user_id) const { - return u != nullptr && u->is_contact && user_id != get_my_id(); +bool ContactsManager::is_user_contact(const User *u, UserId user_id, bool is_mutual) const { + return u != nullptr && (is_mutual ? u->is_mutual_contact : u->is_contact) && user_id != get_my_id(); } -void ContactsManager::on_get_channel_participants_success( - ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset, int32 limit, int64 random_id, - int32 total_count, vector> &&participants) { +void ContactsManager::on_get_channel_participants( + ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset, int32 limit, string additional_query, + int32 additional_limit, tl_object_ptr &&channel_participants, + Promise &&promise) { + if (G()->close_flag()) { + return promise.set_error(Status::Error(500, "Request aborted")); + } + + on_get_users(std::move(channel_participants->users_), "on_get_channel_participants"); + int32 total_count = channel_participants->count_; + auto participants = std::move(channel_participants->participants_); LOG(INFO) << "Receive " << participants.size() << " members in " << channel_id; bool is_full = offset == 0 && static_cast(participants.size()) < limit && total_count < limit; @@ -10649,7 +11486,7 @@ void ContactsManager::on_get_channel_participants_success( vector result; for (auto &participant_ptr : participants) { auto debug_participant = to_string(participant_ptr); - result.push_back(get_dialog_participant(channel_id, std::move(participant_ptr))); + result.emplace_back(std::move(participant_ptr)); const auto &participant = result.back(); if (!participant.is_valid() || (filter.is_bots() && !is_user_bot(participant.user_id)) || (filter.is_administrators() && !participant.status.is_administrator()) || @@ -10711,7 +11548,7 @@ void ContactsManager::on_get_channel_participants_success( } } if (filter.is_administrators() || filter.is_recent()) { - on_update_dialog_administrators(DialogId(channel_id), std::move(administrators), true); + on_update_dialog_administrators(DialogId(channel_id), std::move(administrators), true, false); } if (filter.is_bots() || filter.is_recent()) { on_update_channel_bot_user_ids(channel_id, std::move(bot_user_ids)); @@ -10750,17 +11587,24 @@ void ContactsManager::on_get_channel_participants_success( } } - if (random_id != 0) { - received_channel_participants_[random_id] = {total_count, std::move(result)}; - } -} + if (!additional_query.empty()) { + auto user_ids = transform(result, [](const auto &participant) { return participant.user_id; }); + std::pair> result_user_ids = search_among_users(user_ids, additional_query, additional_limit); -void ContactsManager::on_get_channel_participants_fail(ChannelId channel_id, ChannelParticipantsFilter filter, - int32 offset, int32 limit, int64 random_id) { - if (random_id != 0) { - // clean up - received_channel_participants_.erase(random_id); + total_count = result_user_ids.first; + std::unordered_set result_user_ids_set(result_user_ids.second.begin(), + result_user_ids.second.end()); + auto all_participants = std::move(result); + result.clear(); + for (auto &participant : all_participants) { + if (result_user_ids_set.count(participant.user_id)) { + result.push_back(std::move(participant)); + result_user_ids_set.erase(participant.user_id); + } + } } + + promise.set_value(DialogParticipants{total_count, std::move(result)}); } bool ContactsManager::speculative_add_count(int32 &count, int32 delta_count, int32 min_count) { @@ -10907,14 +11751,14 @@ void ContactsManager::speculative_add_channel_user(ChannelId channel_id, UserId if (administrator.get_rank() != new_status.get_rank() || administrator.is_creator() != new_status.is_creator()) { administrator = DialogAdministrator(user_id, new_status.get_rank(), new_status.is_creator()); - on_update_dialog_administrators(dialog_id, std::move(administrators), true); + on_update_dialog_administrators(dialog_id, std::move(administrators), true, false); } break; } } if (!is_found) { administrators.emplace_back(user_id, new_status.get_rank(), new_status.is_creator()); - on_update_dialog_administrators(dialog_id, std::move(administrators), true); + on_update_dialog_administrators(dialog_id, std::move(administrators), true, false); } } else { size_t i = 0; @@ -10923,7 +11767,7 @@ void ContactsManager::speculative_add_channel_user(ChannelId channel_id, UserId } if (i != administrators.size()) { administrators.erase(administrators.begin() + i); - on_update_dialog_administrators(dialog_id, std::move(administrators), true); + on_update_dialog_administrators(dialog_id, std::move(administrators), true, false); } } } @@ -11026,11 +11870,6 @@ void ContactsManager::invalidate_channel_full(ChannelId channel_id, bool need_dr } if (need_drop_invite_link) { remove_dialog_access_by_invite_link(DialogId(channel_id)); - - auto it = dialog_invite_links_.find(DialogId(channel_id)); - if (it != dialog_invite_links_.end()) { - invalidate_invite_link_info(it->second); - } } } @@ -11108,52 +11947,46 @@ void ContactsManager::on_update_channel_full_photo(ChannelFull *channel_full, Ch } } -void ContactsManager::on_get_chat_invite_link(ChatId chat_id, - tl_object_ptr &&invite_link_ptr) { - CHECK(chat_id.is_valid()); - if (!have_chat_force(chat_id)) { - LOG(ERROR) << chat_id << " not found"; - return; +void ContactsManager::on_get_permanent_dialog_invite_link(DialogId dialog_id, const DialogInviteLink &invite_link) { + switch (dialog_id.get_type()) { + case DialogType::Chat: { + auto chat_id = dialog_id.get_chat_id(); + auto chat_full = get_chat_full_force(chat_id, "on_get_permanent_dialog_invite_link"); + if (chat_full != nullptr && update_permanent_invite_link(chat_full->invite_link, invite_link)) { + chat_full->is_changed = true; + update_chat_full(chat_full, chat_id); + } + break; + } + case DialogType::Channel: { + auto channel_id = dialog_id.get_channel_id(); + auto channel_full = get_channel_full_force(channel_id, "on_get_permanent_dialog_invite_link"); + if (channel_full != nullptr && update_permanent_invite_link(channel_full->invite_link, invite_link)) { + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id); + } + break; + } + case DialogType::User: + case DialogType::SecretChat: + case DialogType::None: + default: + UNREACHABLE(); } - - auto chat_full = get_chat_full_force(chat_id, "on_get_chat_invite_link"); - if (chat_full == nullptr) { - update_invite_link(dialog_invite_links_[DialogId(chat_id)], std::move(invite_link_ptr)); - return; - } - on_update_chat_full_invite_link(chat_full, std::move(invite_link_ptr)); - update_chat_full(chat_full, chat_id); } -void ContactsManager::on_update_chat_full_invite_link( - ChatFull *chat_full, tl_object_ptr &&invite_link_ptr) { +void ContactsManager::on_update_chat_full_invite_link(ChatFull *chat_full, + tl_object_ptr &&invite_link) { CHECK(chat_full != nullptr); - if (update_invite_link(chat_full->invite_link, std::move(invite_link_ptr))) { + if (update_permanent_invite_link(chat_full->invite_link, DialogInviteLink(std::move(invite_link)))) { chat_full->is_changed = true; } } -void ContactsManager::on_get_channel_invite_link(ChannelId channel_id, - tl_object_ptr &&invite_link_ptr) { - CHECK(channel_id.is_valid()); - if (!have_channel(channel_id)) { - LOG(ERROR) << channel_id << " not found"; - return; - } - - auto channel_full = get_channel_full_force(channel_id, "on_get_channel_invite_link"); - if (channel_full == nullptr) { - update_invite_link(dialog_invite_links_[DialogId(channel_id)], std::move(invite_link_ptr)); - return; - } - on_update_channel_full_invite_link(channel_full, std::move(invite_link_ptr)); - update_channel_full(channel_full, channel_id); -} - void ContactsManager::on_update_channel_full_invite_link( - ChannelFull *channel_full, tl_object_ptr &&invite_link_ptr) { + ChannelFull *channel_full, tl_object_ptr &&invite_link) { CHECK(channel_full != nullptr); - if (update_invite_link(channel_full->invite_link, std::move(invite_link_ptr))) { + if (update_permanent_invite_link(channel_full->invite_link, DialogInviteLink(std::move(invite_link)))) { channel_full->is_changed = true; } } @@ -11259,13 +12092,16 @@ void ContactsManager::on_update_channel_full_linked_channel_id(ChannelFull *chan td_->messages_manager_->on_dialog_linked_channel_updated(DialogId(channel_id), old_linked_channel_id, linked_channel_id); } - auto new_linked_linked_channel_id = get_linked_channel_id(linked_channel_id); - LOG(INFO) << "Uplate linked channel in " << linked_channel_id << " from " << old_linked_linked_channel_id << " to " - << new_linked_linked_channel_id; - if (old_linked_linked_channel_id != new_linked_linked_channel_id) { - // must be called after the linked channel is changed - td_->messages_manager_->on_dialog_linked_channel_updated(DialogId(linked_channel_id), old_linked_linked_channel_id, - new_linked_linked_channel_id); + + if (linked_channel_id.is_valid()) { + auto new_linked_linked_channel_id = get_linked_channel_id(linked_channel_id); + LOG(INFO) << "Uplate linked channel in " << linked_channel_id << " from " << old_linked_linked_channel_id << " to " + << new_linked_linked_channel_id; + if (old_linked_linked_channel_id != new_linked_linked_channel_id) { + // must be called after the linked channel is changed + td_->messages_manager_->on_dialog_linked_channel_updated( + DialogId(linked_channel_id), old_linked_linked_channel_id, new_linked_linked_channel_id); + } } } @@ -11373,7 +12209,7 @@ void ContactsManager::on_get_dialog_invite_link_info(const string &invite_link, // the access is already expired, reget the info if (accessible_before != 0 && accessible_before <= G()->unix_time() + 1) { - td_->create_handler(std::move(promise))->send(invite_link); + td_->create_handler(std::move(promise))->send(invite_link); return; } @@ -11393,13 +12229,6 @@ void ContactsManager::on_get_dialog_invite_link_info(const string &invite_link, invite_link_info_expire_timeout_.set_timeout_in(dialog_id.get(), expires_in); } } - - if (chat_id.is_valid()) { - on_get_chat_invite_link(chat_id, make_tl_object(invite_link)); - } - if (channel_id.is_valid()) { - on_get_channel_invite_link(channel_id, make_tl_object(invite_link)); - } break; } case telegram_api::chatInvite::ID: { @@ -11465,56 +12294,12 @@ void ContactsManager::remove_dialog_access_by_invite_link(DialogId dialog_id) { invite_link_info_expire_timeout_.cancel_timeout(dialog_id.get()); } -bool ContactsManager::is_valid_invite_link(const string &invite_link) { - return !get_dialog_invite_link_hash(invite_link).empty(); -} - -Slice ContactsManager::get_dialog_invite_link_hash(const string &invite_link) { - auto lower_cased_invite_link_str = to_lower(invite_link); - Slice lower_cased_invite_link = lower_cased_invite_link_str; - size_t offset = 0; - if (begins_with(lower_cased_invite_link, "https://")) { - offset = 8; - } else if (begins_with(lower_cased_invite_link, "http://")) { - offset = 7; - } - lower_cased_invite_link.remove_prefix(offset); - - for (auto &url : INVITE_LINK_URLS) { - if (begins_with(lower_cased_invite_link, url)) { - Slice hash = Slice(invite_link).substr(url.size() + offset); - hash.truncate(hash.find('#')); - hash.truncate(hash.find('?')); - return hash; - } - } - return Slice(); -} - -bool ContactsManager::update_invite_link(string &invite_link, - tl_object_ptr &&invite_link_ptr) { - string new_invite_link; - if (invite_link_ptr != nullptr) { - switch (invite_link_ptr->get_id()) { - case telegram_api::chatInviteEmpty::ID: - // link is empty - break; - case telegram_api::chatInviteExported::ID: { - auto chat_invite_exported = move_tl_object_as(invite_link_ptr); - new_invite_link = std::move(chat_invite_exported->link_); - break; - } - default: - UNREACHABLE(); - } - } - +bool ContactsManager::update_permanent_invite_link(DialogInviteLink &invite_link, DialogInviteLink new_invite_link) { if (new_invite_link != invite_link) { - if (!invite_link.empty()) { - invite_link_infos_.erase(invite_link); + if (invite_link.is_valid() && invite_link.get_invite_link() != new_invite_link.get_invite_link()) { + // old link was invalidated + invite_link_infos_.erase(invite_link.get_invite_link()); } - LOG_IF(ERROR, !new_invite_link.empty() && !is_valid_invite_link(new_invite_link)) - << "Unsupported invite link " << new_invite_link; invite_link = std::move(new_invite_link); return true; @@ -11729,7 +12514,6 @@ void ContactsManager::on_update_chat_delete_user(ChatId chat_id, UserId user_id, void ContactsManager::on_update_chat_status(Chat *c, ChatId chat_id, DialogParticipantStatus status) { if (c->status != status) { LOG(INFO) << "Update " << chat_id << " status from " << c->status << " to " << status; - bool need_drop_invite_link = c->status.is_left() != status.is_left(); bool need_reload_group_call = c->status.can_manage_calls() != status.can_manage_calls(); c->status = status; @@ -11742,12 +12526,6 @@ void ContactsManager::on_update_chat_status(Chat *c, ChatId chat_id, DialogParti drop_chat_full(chat_id); } - if (need_drop_invite_link) { - auto it = dialog_invite_links_.find(DialogId(chat_id)); - if (it != dialog_invite_links_.end()) { - invalidate_invite_link_info(it->second); - } - } if (need_reload_group_call) { send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_group_call_rights, DialogId(chat_id)); @@ -12021,11 +12799,6 @@ void ContactsManager::drop_chat_full(ChatId chat_id) { ChatFull *chat_full = get_chat_full_force(chat_id, "drop_chat_full"); if (chat_full == nullptr) { drop_chat_photos(chat_id, false, false, "drop_chat_full"); - - auto it = dialog_invite_links_.find(DialogId(chat_id)); - if (it != dialog_invite_links_.end()) { - invalidate_invite_link_info(it->second); - } return; } @@ -12034,7 +12807,7 @@ void ContactsManager::drop_chat_full(ChatId chat_id) { // chat_full->creator_user_id = UserId(); chat_full->participants.clear(); chat_full->version = -1; - update_invite_link(chat_full->invite_link, nullptr); + on_update_chat_full_invite_link(chat_full, nullptr); update_chat_online_member_count(chat_full, chat_id, true); chat_full->is_changed = true; update_chat_full(chat_full, chat_id); @@ -12090,6 +12863,7 @@ void ContactsManager::on_channel_status_changed(Channel *c, ChannelId channel_id send_get_channel_full_query(nullptr, channel_id, Auto(), "update channel owner"); reload_dialog_administrators(DialogId(channel_id), 0, Auto()); + remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); } if (need_reload_group_call) { send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_group_call_rights, @@ -12225,7 +12999,7 @@ void ContactsManager::on_update_channel_bot_user_ids(ChannelId channel_id, vecto auto channel_full = get_channel_full_force(channel_id, "on_update_channel_bot_user_ids"); if (channel_full == nullptr) { - td_->messages_manager_->on_dialog_bots_updated(DialogId(channel_id), std::move(bot_user_ids)); + td_->messages_manager_->on_dialog_bots_updated(DialogId(channel_id), std::move(bot_user_ids), false); return; } on_update_channel_full_bot_user_ids(channel_full, channel_id, std::move(bot_user_ids)); @@ -12236,7 +13010,7 @@ void ContactsManager::on_update_channel_full_bot_user_ids(ChannelFull *channel_f vector &&bot_user_ids) { CHECK(channel_full != nullptr); if (channel_full->bot_user_ids != bot_user_ids) { - td_->messages_manager_->on_dialog_bots_updated(DialogId(channel_id), bot_user_ids); + td_->messages_manager_->on_dialog_bots_updated(DialogId(channel_id), bot_user_ids, false); channel_full->bot_user_ids = std::move(bot_user_ids); channel_full->need_save_to_database = true; } @@ -12273,7 +13047,90 @@ void ContactsManager::on_update_channel_default_permissions(ChannelId channel_id } } +void ContactsManager::send_update_chat_member(DialogId dialog_id, UserId agent_user_id, int32 date, + DialogInviteLink invite_link, + const DialogParticipant &old_dialog_participant, + const DialogParticipant &new_dialog_participant) { + CHECK(td_->auth_manager_->is_bot()); + td_->messages_manager_->force_create_dialog(dialog_id, "send_update_chat_member", true); + send_closure(G()->td(), &Td::send_update, + td_api::make_object( + dialog_id.get(), get_user_id_object(agent_user_id, "send_update_chat_member"), date, + invite_link.get_chat_invite_link_object(this), get_chat_member_object(old_dialog_participant), + get_chat_member_object(new_dialog_participant))); +} + +void ContactsManager::on_update_bot_stopped(UserId user_id, int32 date, bool is_stopped) { + if (!td_->auth_manager_->is_bot()) { + LOG(ERROR) << "Receive updateBotStopped by non-bot"; + return; + } + if (!user_id.is_valid() || date <= 0) { + LOG(ERROR) << "Receive invalid updateBotStopped by " << user_id << " at " << date; + return; + } + if (!have_user_force(user_id)) { + LOG(ERROR) << "Receive updateBotStopped by unknown " << user_id; + return; + } + + DialogParticipant old_dialog_participant(get_my_id(), user_id, date, DialogParticipantStatus::Banned(0)); + DialogParticipant new_dialog_participant(get_my_id(), user_id, date, DialogParticipantStatus::Member()); + if (is_stopped) { + std::swap(old_dialog_participant.status, new_dialog_participant.status); + } + + send_update_chat_member(DialogId(user_id), user_id, date, DialogInviteLink(), old_dialog_participant, + new_dialog_participant); +} + +void ContactsManager::on_update_chat_participant(ChatId chat_id, UserId user_id, int32 date, + DialogInviteLink invite_link, + tl_object_ptr old_participant, + tl_object_ptr new_participant) { + if (!td_->auth_manager_->is_bot()) { + LOG(ERROR) << "Receive updateChatParticipant by non-bot"; + return; + } + if (!chat_id.is_valid() || !user_id.is_valid() || date <= 0 || + (old_participant == nullptr && new_participant == nullptr)) { + LOG(ERROR) << "Receive invalid updateChatParticipant in " << chat_id << " for " << user_id << " at " << date << ": " + << to_string(old_participant) << " -> " << to_string(new_participant); + return; + } + + const Chat *c = get_chat(chat_id); + if (c == nullptr) { + LOG(ERROR) << "Receive updateChatParticipant in unknown " << chat_id; + return; + } + + DialogParticipant old_dialog_participant; + DialogParticipant new_dialog_participant; + if (old_participant != nullptr) { + old_dialog_participant = DialogParticipant(std::move(old_participant), c->date, c->status.is_creator()); + if (new_participant == nullptr) { + new_dialog_participant = DialogParticipant::left(old_dialog_participant.user_id); + } else { + new_dialog_participant = DialogParticipant(std::move(new_participant), c->date, c->status.is_creator()); + } + } else { + new_dialog_participant = DialogParticipant(std::move(new_participant), c->date, c->status.is_creator()); + old_dialog_participant = DialogParticipant::left(new_dialog_participant.user_id); + } + if (old_dialog_participant.user_id != new_dialog_participant.user_id || !old_dialog_participant.is_valid() || + !new_dialog_participant.is_valid()) { + LOG(ERROR) << "Receive wrong updateChannelParticipant: " << old_dialog_participant << " -> " + << new_dialog_participant; + return; + } + + send_update_chat_member(DialogId(chat_id), user_id, date, invite_link, old_dialog_participant, + new_dialog_participant); +} + void ContactsManager::on_update_channel_participant(ChannelId channel_id, UserId user_id, int32 date, + DialogInviteLink invite_link, tl_object_ptr old_participant, tl_object_ptr new_participant) { if (!td_->auth_manager_->is_bot()) { @@ -12290,14 +13147,14 @@ void ContactsManager::on_update_channel_participant(ChannelId channel_id, UserId DialogParticipant old_dialog_participant; DialogParticipant new_dialog_participant; if (old_participant != nullptr) { - old_dialog_participant = get_dialog_participant(channel_id, std::move(old_participant)); + old_dialog_participant = DialogParticipant(std::move(old_participant)); if (new_participant == nullptr) { new_dialog_participant = DialogParticipant::left(old_dialog_participant.user_id); } else { - new_dialog_participant = get_dialog_participant(channel_id, std::move(new_participant)); + new_dialog_participant = DialogParticipant(std::move(new_participant)); } } else { - new_dialog_participant = get_dialog_participant(channel_id, std::move(new_participant)); + new_dialog_participant = DialogParticipant(std::move(new_participant)); old_dialog_participant = DialogParticipant::left(new_dialog_participant.user_id); } if (old_dialog_participant.user_id != new_dialog_participant.user_id || !old_dialog_participant.is_valid() || @@ -12307,11 +13164,12 @@ void ContactsManager::on_update_channel_participant(ChannelId channel_id, UserId return; } - // TODO send update + send_update_chat_member(DialogId(channel_id), user_id, date, invite_link, old_dialog_participant, + new_dialog_participant); } void ContactsManager::update_contacts_hints(const User *u, UserId user_id, bool from_database) { - bool is_contact = is_user_contact(u, user_id); + bool is_contact = is_user_contact(u, user_id, false); if (td_->auth_manager_->is_bot()) { LOG_IF(ERROR, is_contact) << "Bot has " << user_id << " in the contacts list"; return; @@ -12899,12 +13757,19 @@ bool ContactsManager::is_chat_full_outdated(const ChatFull *chat_full, const Cha for (const auto &participant : chat_full->participants) { auto u = get_user(participant.user_id); if (u != nullptr && is_bot_info_expired(participant.user_id, u->bot_info_version)) { - LOG(INFO) << "Have outdated botInfo for " << participant.user_id << ", expected version " << u->bot_info_version; + LOG(INFO) << "Have outdated botInfo for " << participant.user_id << " in " << chat_id << "; expected version " + << u->bot_info_version; return true; } } - LOG(INFO) << "Full " << chat_id << " is up-to-date with version " << chat_full->version; + if (c->is_active && c->status.is_administrator() && c->status.can_invite_users() && + !chat_full->invite_link.is_valid()) { + LOG(INFO) << "Have outdated invite link in " << chat_id; + return true; + } + + LOG(DEBUG) << "Full " << chat_id << " is up-to-date with version " << chat_full->version; return false; } @@ -13370,7 +14235,8 @@ void ContactsManager::on_update_secret_chat(SecretChatId secret_chat_id, int64 a if (ttl != -1 && ttl != secret_chat->ttl) { secret_chat->ttl = ttl; - secret_chat->is_changed = true; + secret_chat->need_save_to_database = true; + secret_chat->is_ttl_changed = true; } if (date != 0 && date != secret_chat->date) { secret_chat->date = date; @@ -13393,7 +14259,7 @@ void ContactsManager::on_update_secret_chat(SecretChatId secret_chat_id, int64 a } std::pair> ContactsManager::search_among_users(const vector &user_ids, - const string &query, int32 limit) { + const string &query, int32 limit) const { Hints hints; // TODO cache Hints for (auto user_id : user_ids) { @@ -13414,6 +14280,272 @@ std::pair> ContactsManager::search_among_users(const vecto transform(result.second, [](int64 key) { return UserId(narrow_cast(key)); })}; } +void ContactsManager::add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit, + Promise &&promise) { + if (!td_->messages_manager_->have_dialog_force(dialog_id)) { + return promise.set_error(Status::Error(3, "Chat not found")); + } + + switch (dialog_id.get_type()) { + case DialogType::User: + return promise.set_error(Status::Error(3, "Can't add members to a private chat")); + case DialogType::Chat: + return add_chat_participant(dialog_id.get_chat_id(), user_id, forward_limit, std::move(promise)); + case DialogType::Channel: + return add_channel_participant(dialog_id.get_channel_id(), user_id, std::move(promise)); + case DialogType::SecretChat: + return promise.set_error(Status::Error(3, "Can't add members to a secret chat")); + case DialogType::None: + default: + UNREACHABLE(); + } +} + +void ContactsManager::add_dialog_participants(DialogId dialog_id, const vector &user_ids, + Promise &&promise) { + if (td_->auth_manager_->is_bot()) { + return promise.set_error(Status::Error(3, "Method is not available for bots")); + } + + if (!td_->messages_manager_->have_dialog_force(dialog_id)) { + return promise.set_error(Status::Error(3, "Chat not found")); + } + + switch (dialog_id.get_type()) { + case DialogType::User: + return promise.set_error(Status::Error(3, "Can't add members to a private chat")); + case DialogType::Chat: + return promise.set_error(Status::Error(3, "Can't add many members at once to a basic group chat")); + case DialogType::Channel: + return add_channel_participants(dialog_id.get_channel_id(), user_ids, std::move(promise)); + case DialogType::SecretChat: + return promise.set_error(Status::Error(3, "Can't add members to a secret chat")); + case DialogType::None: + default: + UNREACHABLE(); + } +} + +void ContactsManager::set_dialog_participant_status(DialogId dialog_id, UserId user_id, + const tl_object_ptr &chat_member_status, + Promise &&promise) { + auto status = get_dialog_participant_status(chat_member_status); + if (!td_->messages_manager_->have_dialog_force(dialog_id)) { + return promise.set_error(Status::Error(3, "Chat not found")); + } + + switch (dialog_id.get_type()) { + case DialogType::User: + return promise.set_error(Status::Error(3, "Chat member status can't be changed in private chats")); + case DialogType::Chat: + return change_chat_participant_status(dialog_id.get_chat_id(), user_id, status, std::move(promise)); + case DialogType::Channel: + return change_channel_participant_status(dialog_id.get_channel_id(), user_id, status, std::move(promise)); + case DialogType::SecretChat: + return promise.set_error(Status::Error(3, "Chat member status can't be changed in secret chats")); + case DialogType::None: + default: + UNREACHABLE(); + } +} + +void ContactsManager::ban_dialog_participant(DialogId dialog_id, UserId user_id, int32 banned_until_date, + bool revoke_messages, Promise &&promise) { + if (!td_->messages_manager_->have_dialog_force(dialog_id)) { + return promise.set_error(Status::Error(3, "Chat not found")); + } + + switch (dialog_id.get_type()) { + case DialogType::User: + return promise.set_error(Status::Error(3, "Can't ban members in a private chat")); + case DialogType::Chat: + return delete_chat_participant(dialog_id.get_chat_id(), user_id, revoke_messages, std::move(promise)); + case DialogType::Channel: + return change_channel_participant_status(dialog_id.get_channel_id(), user_id, + DialogParticipantStatus::Banned(banned_until_date), std::move(promise)); + case DialogType::SecretChat: + return promise.set_error(Status::Error(3, "Can't ban members in a secret chat")); + case DialogType::None: + default: + UNREACHABLE(); + } +} + +DialogParticipant ContactsManager::get_dialog_participant(DialogId dialog_id, UserId user_id, int64 &random_id, + bool force, Promise &&promise) { + LOG(INFO) << "Receive GetChatMember request to get " << user_id << " in " << dialog_id << " with random_id " + << random_id; + if (!td_->messages_manager_->have_dialog_force(dialog_id)) { + promise.set_error(Status::Error(3, "Chat not found")); + return DialogParticipant(); + } + + switch (dialog_id.get_type()) { + case DialogType::User: { + auto peer_user_id = dialog_id.get_user_id(); + if (user_id == get_my_id()) { + promise.set_value(Unit()); + return {user_id, peer_user_id, 0, DialogParticipantStatus::Member()}; + } + if (user_id == peer_user_id) { + promise.set_value(Unit()); + return {peer_user_id, user_id, 0, DialogParticipantStatus::Member()}; + } + + promise.set_error(Status::Error(3, "User is not a member of the private chat")); + break; + } + case DialogType::Chat: + return get_chat_participant(dialog_id.get_chat_id(), user_id, force, std::move(promise)); + case DialogType::Channel: + return get_channel_participant(dialog_id.get_channel_id(), user_id, random_id, force, std::move(promise)); + case DialogType::SecretChat: { + auto peer_user_id = get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + if (user_id == get_my_id()) { + promise.set_value(Unit()); + return {user_id, peer_user_id.is_valid() ? peer_user_id : user_id, 0, DialogParticipantStatus::Member()}; + } + if (peer_user_id.is_valid() && user_id == peer_user_id) { + promise.set_value(Unit()); + return {peer_user_id, user_id, 0, DialogParticipantStatus::Member()}; + } + + promise.set_error(Status::Error(3, "User is not a member of the secret chat")); + break; + } + case DialogType::None: + default: + UNREACHABLE(); + promise.set_error(Status::Error(500, "Wrong chat type")); + } + return DialogParticipant(); +} + +DialogParticipants ContactsManager::search_private_chat_participants(UserId my_user_id, UserId peer_user_id, + const string &query, int32 limit, + DialogParticipantsFilter filter) const { + vector user_ids; + switch (filter.type) { + case DialogParticipantsFilter::Type::Contacts: + if (peer_user_id.is_valid() && is_user_contact(peer_user_id)) { + user_ids.push_back(peer_user_id); + } + break; + case DialogParticipantsFilter::Type::Administrators: + break; + case DialogParticipantsFilter::Type::Members: + case DialogParticipantsFilter::Type::Mention: + user_ids.push_back(my_user_id); + if (peer_user_id.is_valid() && peer_user_id != my_user_id) { + user_ids.push_back(peer_user_id); + } + break; + case DialogParticipantsFilter::Type::Restricted: + break; + case DialogParticipantsFilter::Type::Banned: + break; + case DialogParticipantsFilter::Type::Bots: + if (td_->auth_manager_->is_bot()) { + user_ids.push_back(my_user_id); + } + if (peer_user_id.is_valid() && is_user_bot(peer_user_id) && peer_user_id != my_user_id) { + user_ids.push_back(peer_user_id); + } + break; + default: + UNREACHABLE(); + } + + auto result = search_among_users(user_ids, query, limit); + return {result.first, transform(result.second, [&](UserId user_id) { + return DialogParticipant(user_id, + user_id == my_user_id && peer_user_id.is_valid() ? peer_user_id : my_user_id, 0, + DialogParticipantStatus::Member()); + })}; +} + +void ContactsManager::search_dialog_participants(DialogId dialog_id, const string &query, int32 limit, + DialogParticipantsFilter filter, bool without_bot_info, + Promise &&promise) { + LOG(INFO) << "Receive searchChatMembers request to search for \"" << query << "\" in " << dialog_id << " with filter " + << filter; + if (!td_->messages_manager_->have_dialog_force(dialog_id)) { + return promise.set_error(Status::Error(3, "Chat not found")); + } + if (limit < 0) { + return promise.set_error(Status::Error(3, "Parameter limit must be non-negative")); + } + + switch (dialog_id.get_type()) { + case DialogType::User: + promise.set_value(search_private_chat_participants(get_my_id(), dialog_id.get_user_id(), query, limit, filter)); + return; + case DialogType::Chat: + return search_chat_participants(dialog_id.get_chat_id(), query, limit, filter, std::move(promise)); + case DialogType::Channel: { + td_api::object_ptr request_filter; + string additional_query; + int32 additional_limit = 0; + switch (filter.type) { + case DialogParticipantsFilter::Type::Contacts: + request_filter = td_api::make_object(); + break; + case DialogParticipantsFilter::Type::Administrators: + request_filter = td_api::make_object(); + break; + case DialogParticipantsFilter::Type::Members: + request_filter = td_api::make_object(query); + break; + case DialogParticipantsFilter::Type::Restricted: + request_filter = td_api::make_object(query); + break; + case DialogParticipantsFilter::Type::Banned: + request_filter = td_api::make_object(query); + break; + case DialogParticipantsFilter::Type::Mention: + request_filter = + td_api::make_object(query, filter.top_thread_message_id.get()); + break; + case DialogParticipantsFilter::Type::Bots: + request_filter = td_api::make_object(); + break; + default: + UNREACHABLE(); + } + switch (filter.type) { + case DialogParticipantsFilter::Type::Contacts: + case DialogParticipantsFilter::Type::Administrators: + case DialogParticipantsFilter::Type::Bots: + additional_query = query; + additional_limit = limit; + limit = 100; + break; + case DialogParticipantsFilter::Type::Members: + case DialogParticipantsFilter::Type::Restricted: + case DialogParticipantsFilter::Type::Banned: + case DialogParticipantsFilter::Type::Mention: + // query is passed to the server request + break; + default: + UNREACHABLE(); + } + + return get_channel_participants(dialog_id.get_channel_id(), std::move(request_filter), + std::move(additional_query), 0, limit, additional_limit, without_bot_info, + std::move(promise)); + } + case DialogType::SecretChat: { + auto peer_user_id = get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + promise.set_value(search_private_chat_participants(get_my_id(), peer_user_id, query, limit, filter)); + return; + } + case DialogType::None: + default: + UNREACHABLE(); + promise.set_error(Status::Error(500, "Wrong chat type")); + } +} + DialogParticipant ContactsManager::get_chat_participant(ChatId chat_id, UserId user_id, bool force, Promise &&promise) { LOG(INFO) << "Trying to get " << user_id << " as member of " << chat_id; @@ -13432,26 +14564,34 @@ DialogParticipant ContactsManager::get_chat_participant(ChatId chat_id, UserId u return *result; } -std::pair> ContactsManager::search_chat_participants(ChatId chat_id, - const string &query, int32 limit, - DialogParticipantsFilter filter, - bool force, - Promise &&promise) { +void ContactsManager::search_chat_participants(ChatId chat_id, const string &query, int32 limit, + DialogParticipantsFilter filter, Promise &&promise) { if (limit < 0) { - promise.set_error(Status::Error(3, "Parameter limit must be non-negative")); - return {}; + return promise.set_error(Status::Error(3, "Parameter limit must be non-negative")); } - if (force) { - promise.set_value(Unit()); - } else if (!load_chat_full(chat_id, force, std::move(promise), "search_chat_participants")) { - return {}; + auto load_chat_full_promise = PromiseCreator::lambda([actor_id = actor_id(this), chat_id, query, limit, filter, + promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &ContactsManager::do_search_chat_participants, chat_id, query, limit, filter, + std::move(promise)); + } + }); + load_chat_full(chat_id, false, std::move(load_chat_full_promise), "search_chat_participants"); +} + +void ContactsManager::do_search_chat_participants(ChatId chat_id, const string &query, int32 limit, + DialogParticipantsFilter filter, + Promise &&promise) { + if (G()->close_flag()) { + return promise.set_error(Status::Error(500, "Request aborted")); } - // promise is already set auto chat_full = get_chat_full(chat_id); if (chat_full == nullptr) { - return {}; + return promise.set_error(Status::Error(500, "Can't find basic group full info")); } auto is_dialog_participant_suitable = [this, filter](const DialogParticipant &participant) { @@ -13485,7 +14625,9 @@ std::pair> ContactsManager::search_chat_partici int32 total_count; std::tie(total_count, user_ids) = search_among_users(user_ids, query, limit); - return {total_count, transform(user_ids, [&](UserId user_id) { return *get_chat_participant(chat_full, user_id); })}; + promise.set_value(DialogParticipants{total_count, transform(user_ids, [chat_full](UserId user_id) { + return *ContactsManager::get_chat_full_participant(chat_full, user_id); + })}); } DialogParticipant ContactsManager::get_channel_participant(ChannelId channel_id, UserId user_id, int64 &random_id, @@ -13549,92 +14691,93 @@ DialogParticipant ContactsManager::get_channel_participant(ChannelId channel_id, return DialogParticipant(); } -std::pair> ContactsManager::get_channel_participants( - ChannelId channel_id, const tl_object_ptr &filter, const string &additional_query, - int32 offset, int32 limit, int32 additional_limit, int64 &random_id, bool without_bot_info, bool force, - Promise &&promise) { - if (random_id != 0) { - // request has already been sent before - auto it = received_channel_participants_.find(random_id); - CHECK(it != received_channel_participants_.end()); - auto result = std::move(it->second); - received_channel_participants_.erase(it); - promise.set_value(Unit()); - - if (additional_query.empty()) { - return result; - } - - auto user_ids = transform(result.second, [](const auto &participant) { return participant.user_id; }); - std::pair> result_user_ids = search_among_users(user_ids, additional_query, additional_limit); - - result.first = result_user_ids.first; - std::unordered_set result_user_ids_set(result_user_ids.second.begin(), - result_user_ids.second.end()); - auto all_participants = std::move(result.second); - result.second.clear(); - for (auto &participant : all_participants) { - if (result_user_ids_set.count(participant.user_id)) { - result.second.push_back(std::move(participant)); - result_user_ids_set.erase(participant.user_id); - } - } - return result; - } - - std::pair> result; +void ContactsManager::get_channel_participants(ChannelId channel_id, + tl_object_ptr &&filter, + string additional_query, int32 offset, int32 limit, + int32 additional_limit, bool without_bot_info, + Promise &&promise) { if (limit <= 0) { - promise.set_error(Status::Error(3, "Parameter limit must be positive")); - return result; + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); } if (limit > MAX_GET_CHANNEL_PARTICIPANTS) { limit = MAX_GET_CHANNEL_PARTICIPANTS; } if (offset < 0) { - promise.set_error(Status::Error(3, "Parameter offset must be non-negative")); - return result; + return promise.set_error(Status::Error(400, "Parameter offset must be non-negative")); } - auto channel_full = get_channel_full_force(channel_id, "get_channel_participants"); - if (td_->auth_manager_->is_bot()) { - without_bot_info = true; - } - if (!without_bot_info && (channel_full == nullptr || (!force && channel_full->is_expired()))) { - if (force) { - LOG(ERROR) << "Can't find cached ChannelFull"; - } else { - send_get_channel_full_query(channel_full, channel_id, std::move(promise), "get_channel_participants"); - return result; + auto load_channel_full_promise = + PromiseCreator::lambda([actor_id = actor_id(this), channel_id, filter = ChannelParticipantsFilter(filter), + additional_query = std::move(additional_query), offset, limit, additional_limit, + promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &ContactsManager::do_get_channel_participants, channel_id, std::move(filter), + std::move(additional_query), offset, limit, additional_limit, std::move(promise)); + } + }); + if (!without_bot_info && !td_->auth_manager_->is_bot()) { + auto channel_full = get_channel_full_force(channel_id, "get_channel_participants"); + if (channel_full == nullptr || channel_full->is_expired()) { + send_get_channel_full_query(channel_full, channel_id, std::move(load_channel_full_promise), + "get_channel_participants"); + return; } } - - if (channel_full != nullptr && !channel_full->is_expired() && !channel_full->can_get_participants) { - promise.set_error(Status::Error(3, "Member list is inaccessible")); - return result; - } - - do { - random_id = Random::secure_int64(); - } while (random_id == 0 || received_channel_participants_.find(random_id) != received_channel_participants_.end()); - received_channel_participants_[random_id]; // reserve place for result - - send_get_channel_participants_query(channel_id, ChannelParticipantsFilter(filter), offset, limit, random_id, - std::move(promise)); - return result; + load_channel_full_promise.set_value(Unit()); } -void ContactsManager::send_get_channel_participants_query(ChannelId channel_id, ChannelParticipantsFilter filter, - int32 offset, int32 limit, int64 random_id, - Promise &&promise) { - LOG(DEBUG) << "Get members of the " << channel_id << " with filter " << filter << ", offset = " << offset - << " and limit = " << limit; - td_->create_handler(std::move(promise)) - ->send(channel_id, std::move(filter), offset, limit, random_id); +void ContactsManager::do_get_channel_participants(ChannelId channel_id, ChannelParticipantsFilter &&filter, + string additional_query, int32 offset, int32 limit, + int32 additional_limit, Promise &&promise) { + if (G()->close_flag()) { + return promise.set_error(Status::Error(500, "Request aborted")); + } + + auto channel_full = get_channel_full_force(channel_id, "do_get_channel_participants"); + if (channel_full != nullptr && !channel_full->is_expired() && !channel_full->can_get_participants) { + return promise.set_error(Status::Error(400, "Member list is inaccessible")); + } + + auto get_channel_participants_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), channel_id, filter, additional_query = std::move(additional_query), offset, limit, + additional_limit, promise = std::move(promise)]( + Result> &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &ContactsManager::on_get_channel_participants, channel_id, std::move(filter), offset, + limit, std::move(additional_query), additional_limit, result.move_as_ok(), std::move(promise)); + } + }); + td_->create_handler(std::move(get_channel_participants_promise)) + ->send(channel_id, std::move(filter), offset, limit); } vector ContactsManager::get_dialog_administrators(DialogId dialog_id, int left_tries, Promise &&promise) { + LOG(INFO) << "Receive GetChatAdministrators request in " << dialog_id << " with " << left_tries << " left tries"; + if (!td_->messages_manager_->have_dialog_force(dialog_id)) { + promise.set_error(Status::Error(3, "Chat not found")); + return {}; + } + + switch (dialog_id.get_type()) { + case DialogType::User: + case DialogType::SecretChat: + promise.set_value(Unit()); + return {}; + case DialogType::Chat: + case DialogType::Channel: + break; + case DialogType::None: + default: + UNREACHABLE(); + return {}; + } + auto it = dialog_administrators_.find(dialog_id); if (it != dialog_administrators_.end()) { promise.set_value(Unit()); @@ -13740,7 +14883,7 @@ void ContactsManager::on_update_channel_administrator_count(ChannelId channel_id } void ContactsManager::on_update_dialog_administrators(DialogId dialog_id, vector &&administrators, - bool have_access) { + bool have_access, bool from_database) { LOG(INFO) << "Update administrators in " << dialog_id << " to " << format::as_array(administrators); if (have_access) { std::sort(administrators.begin(), administrators.end(), @@ -13758,7 +14901,7 @@ void ContactsManager::on_update_dialog_administrators(DialogId dialog_id, vector it = dialog_administrators_.emplace(dialog_id, std::move(administrators)).first; } - if (G()->parameters().use_chat_info_db) { + if (G()->parameters().use_chat_info_db && !from_database) { LOG(INFO) << "Save administrators of " << dialog_id << " to database"; G()->td_db()->get_sqlite_pmc()->set(get_dialog_administrators_database_key(dialog_id), log_event_store(it->second).as_slice().str(), Auto()); @@ -13883,7 +15026,7 @@ void ContactsManager::on_chat_update(telegram_api::chat &chat, const char *sourc on_update_chat_photo(c, chat_id, std::move(chat.photo_)); on_update_chat_active(c, chat_id, is_active); on_update_chat_migrated_to_channel_id(c, chat_id, migrated_to_channel_id); - LOG_IF(INFO, !is_active && !migrated_to_channel_id.is_valid()) << chat_id << " is deactivated in " << debug_str; + LOG_IF(INFO, !is_active && !migrated_to_channel_id.is_valid()) << chat_id << " is deactivated" << debug_str; if (c->cache_version != Chat::CACHE_VERSION) { c->cache_version = Chat::CACHE_VERSION; c->need_save_to_database = true; @@ -13957,6 +15100,8 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char bool is_verified = (channel.flags_ & CHANNEL_FLAG_IS_VERIFIED) != 0; auto restriction_reasons = get_restriction_reasons(std::move(channel.restriction_reason_)); bool is_scam = (channel.flags_ & CHANNEL_FLAG_IS_SCAM) != 0; + bool is_fake = (channel.flags_ & CHANNEL_FLAG_IS_FAKE) != 0; + bool is_gigagroup = (channel.flags_ & CHANNEL_FLAG_IS_GIGAGROUP) != 0; bool have_participant_count = (channel.flags_ & CHANNEL_FLAG_HAS_PARTICIPANT_COUNT) != 0; int32 participant_count = have_participant_count ? channel.participants_count_ : 0; @@ -13979,7 +15124,12 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char sign_messages = true; } else { LOG_IF(ERROR, is_slow_mode_enabled) << "Slow mode enabled in the " << channel_id << " from " << source; + LOG_IF(ERROR, is_gigagroup) << "Receive broadcast group as channel " << channel_id << " from " << source; is_slow_mode_enabled = false; + is_gigagroup = false; + } + if (is_gigagroup) { + remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); } DialogParticipantStatus status = [&] { @@ -14012,13 +15162,28 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char on_update_channel_default_permissions(c, channel_id, get_restricted_rights(std::move(channel.default_banned_rights_))); - if (c->is_megagroup != is_megagroup || c->is_verified != is_verified) { + if (c->has_linked_channel != has_linked_channel || c->has_location != has_location || + c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup || + c->restriction_reasons != restriction_reasons || c->is_scam != is_scam || c->is_fake != is_fake || + c->is_gigagroup != is_gigagroup) { + c->has_linked_channel = has_linked_channel; + c->has_location = has_location; + c->is_slow_mode_enabled = is_slow_mode_enabled; c->is_megagroup = is_megagroup; - c->is_verified = is_verified; + c->restriction_reasons = std::move(restriction_reasons); + c->is_scam = is_scam; + c->is_fake = is_fake; + c->is_gigagroup = is_gigagroup; c->is_changed = true; invalidate_channel_full(channel_id, false, !c->is_slow_mode_enabled); } + if (c->is_verified != is_verified || c->sign_messages != sign_messages) { + c->is_verified = is_verified; + c->sign_messages = sign_messages; + + c->is_changed = true; + } update_channel(c, channel_id); } else { @@ -14064,13 +15229,16 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char bool need_invalidate_channel_full = false; if (c->has_linked_channel != has_linked_channel || c->has_location != has_location || c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup || - c->restriction_reasons != restriction_reasons || c->is_scam != is_scam) { + c->restriction_reasons != restriction_reasons || c->is_scam != is_scam || c->is_fake != is_fake || + c->is_gigagroup != is_gigagroup) { c->has_linked_channel = has_linked_channel; c->has_location = has_location; c->is_slow_mode_enabled = is_slow_mode_enabled; c->is_megagroup = is_megagroup; c->restriction_reasons = std::move(restriction_reasons); c->is_scam = is_scam; + c->is_fake = is_fake; + c->is_gigagroup = is_gigagroup; c->is_changed = true; need_invalidate_channel_full = true; @@ -14151,6 +15319,7 @@ void ContactsManager::on_chat_update(telegram_api::channelForbidden &channel, co bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0; bool is_verified = false; bool is_scam = false; + bool is_fake = false; { bool is_broadcast = (channel.flags_ & CHANNEL_FLAG_IS_BROADCAST) != 0; @@ -14165,13 +15334,14 @@ void ContactsManager::on_chat_update(telegram_api::channelForbidden &channel, co bool need_invalidate_channel_full = false; if (c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup || - !c->restriction_reasons.empty() || c->is_scam != is_scam) { + !c->restriction_reasons.empty() || c->is_scam != is_scam || c->is_fake != is_fake) { // c->has_linked_channel = has_linked_channel; // c->has_location = has_location; c->is_slow_mode_enabled = is_slow_mode_enabled; c->is_megagroup = is_megagroup; c->restriction_reasons.clear(); c->is_scam = is_scam; + c->is_fake = is_fake; c->is_changed = true; need_invalidate_channel_full = true; @@ -14294,7 +15464,7 @@ td_api::object_ptr ContactsManager::get_user_status_object(U td_api::object_ptr ContactsManager::get_update_unknown_user_object(UserId user_id) { return td_api::make_object(td_api::make_object( user_id.get(), "", "", "", "", td_api::make_object(), nullptr, false, false, false, - false, "", false, false, td_api::make_object(), "")); + false, "", false, false, false, td_api::make_object(), "")); } int32 ContactsManager::get_user_id_object(UserId user_id, const char *source) const { @@ -14327,7 +15497,7 @@ tl_object_ptr ContactsManager::get_user_object(UserId user_id, con return make_tl_object( user_id.get(), u->first_name, u->last_name, u->username, u->phone_number, get_user_status_object(user_id, u), get_profile_photo_object(td_->file_manager_.get(), u->photo), u->is_contact, u->is_mutual_contact, u->is_verified, - u->is_support, get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_received, + u->is_support, get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, u->is_received, std::move(type), u->language_code); } @@ -14405,14 +15575,14 @@ tl_object_ptr ContactsManager::get_basic_group_full_ get_user_id_object(chat_full->creator_user_id, "basicGroupFullInfo"), transform(chat_full->participants, [this](const DialogParticipant &chat_participant) { return get_chat_member_object(chat_participant); }), - chat_full->invite_link); + chat_full->invite_link.get_chat_invite_link_object(this)); } td_api::object_ptr ContactsManager::get_update_unknown_supergroup_object( ChannelId channel_id) { return td_api::make_object(td_api::make_object( channel_id.get(), string(), 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), 0, false, - false, false, false, true, false, "", false)); + false, false, false, true, false, false, string(), false, false)); } int32 ContactsManager::get_supergroup_id_object(ChannelId channel_id, const char *source) const { @@ -14435,7 +15605,8 @@ tl_object_ptr ContactsManager::get_supergroup_object(Channel return td_api::make_object( channel_id.get(), c->username, c->date, get_channel_status(c).get_chat_member_status_object(), c->participant_count, c->has_linked_channel, c->has_location, c->sign_messages, c->is_slow_mode_enabled, - !c->is_megagroup, c->is_verified, get_restriction_reason_description(c->restriction_reasons), c->is_scam); + !c->is_megagroup, c->is_gigagroup, c->is_verified, get_restriction_reason_description(c->restriction_reasons), + c->is_scam, c->is_fake); } tl_object_ptr ContactsManager::get_supergroup_full_info_object(ChannelId channel_id) const { @@ -14458,7 +15629,7 @@ tl_object_ptr ContactsManager::get_supergroup_full_i slow_mode_delay_expires_in, channel_full->can_get_participants, channel_full->can_set_username, channel_full->can_set_sticker_set, channel_full->can_set_location, channel_full->can_view_statistics, channel_full->is_all_history_available, channel_full->sticker_set_id.get(), - channel_full->location.get_chat_location_object(), channel_full->invite_link, + channel_full->location.get_chat_location_object(), channel_full->invite_link.get_chat_invite_link_object(this), get_basic_group_id_object(channel_full->migrated_from_chat_id, "get_supergroup_full_info_object"), channel_full->migrated_from_max_message_id.get()); } @@ -14481,7 +15652,7 @@ tl_object_ptr ContactsManager::get_secret_chat_state_ob td_api::object_ptr ContactsManager::get_update_unknown_secret_chat_object( SecretChatId secret_chat_id) { return td_api::make_object(td_api::make_object( - secret_chat_id.get(), 0, get_secret_chat_state_object(SecretChatState::Unknown), false, 0, string(), 0)); + secret_chat_id.get(), 0, get_secret_chat_state_object(SecretChatState::Unknown), false, string(), 0)); } int32 ContactsManager::get_secret_chat_id_object(SecretChatId secret_chat_id, const char *source) const { @@ -14509,10 +15680,10 @@ tl_object_ptr ContactsManager::get_secret_chat_object(Secret tl_object_ptr ContactsManager::get_secret_chat_object_const(SecretChatId secret_chat_id, const SecretChat *secret_chat) const { - return td_api::make_object( - secret_chat_id.get(), get_user_id_object(secret_chat->user_id, "secretChat"), - get_secret_chat_state_object(secret_chat->state), secret_chat->is_outbound, secret_chat->ttl, - secret_chat->key_hash, secret_chat->layer); + return td_api::make_object(secret_chat_id.get(), + get_user_id_object(secret_chat->user_id, "secretChat"), + get_secret_chat_state_object(secret_chat->state), + secret_chat->is_outbound, secret_chat->key_hash, secret_chat->layer); } td_api::object_ptr ContactsManager::get_bot_info_object(UserId user_id) const { diff --git a/td/telegram/ContactsManager.h b/td/telegram/ContactsManager.h index a429666cb..3c3c8e21f 100644 --- a/td/telegram/ContactsManager.h +++ b/td/telegram/ContactsManager.h @@ -15,6 +15,7 @@ #include "td/telegram/Contact.h" #include "td/telegram/DialogAdministrator.h" #include "td/telegram/DialogId.h" +#include "td/telegram/DialogInviteLink.h" #include "td/telegram/DialogLocation.h" #include "td/telegram/DialogParticipant.h" #include "td/telegram/files/FileId.h" @@ -30,6 +31,7 @@ #include "td/telegram/RestrictionReason.h" #include "td/telegram/SecretChatId.h" #include "td/telegram/StickerSetId.h" +#include "td/telegram/SuggestedAction.h" #include "td/telegram/UserId.h" #include "td/actor/actor.h" @@ -39,9 +41,9 @@ #include "td/utils/common.h" #include "td/utils/Hints.h" -#include "td/utils/Slice.h" #include "td/utils/Status.h" #include "td/utils/StringBuilder.h" +#include "td/utils/Time.h" #include #include @@ -53,6 +55,9 @@ namespace td { struct BinlogEvent; +class DialogInviteLink; +class DialogLocation; + class Td; struct BotData { @@ -76,6 +81,11 @@ struct CanTransferOwnershipResult { class ContactsManager : public Actor { public: ContactsManager(Td *td, ActorShared<> parent); + ContactsManager(const ContactsManager &) = delete; + ContactsManager &operator=(const ContactsManager &) = delete; + ContactsManager(ContactsManager &&) = delete; + ContactsManager &operator=(ContactsManager &&) = delete; + ~ContactsManager() override; static UserId load_my_id(); @@ -160,7 +170,7 @@ class ContactsManager : public Actor { void on_binlog_channel_event(BinlogEvent &&event); void on_binlog_secret_chat_event(BinlogEvent &&event); - void on_get_user_full(tl_object_ptr &&user_full); + void on_get_user_full(tl_object_ptr &&user); void on_get_user_photos(UserId user_id, int32 offset, int32 limit, int32 total_count, vector> photos); @@ -206,14 +216,19 @@ class ContactsManager : public Actor { Promise &&promise); void on_update_channel_default_permissions(ChannelId channel_id, RestrictedRights default_permissions); void on_update_channel_administrator_count(ChannelId channel_id, int32 administrator_count); - void on_update_channel_participant(ChannelId channel_id, UserId user_id, int32 date, + + void on_update_bot_stopped(UserId user_id, int32 date, bool is_stopped); + void on_update_chat_participant(ChatId chat_id, UserId user_id, int32 date, DialogInviteLink invite_link, + tl_object_ptr old_participant, + tl_object_ptr new_participant); + void on_update_channel_participant(ChannelId channel_id, UserId user_id, int32 date, DialogInviteLink invite_link, tl_object_ptr old_participant, tl_object_ptr new_participant); int32 on_update_peer_located(vector> &&peers, bool from_update); void on_update_dialog_administrators(DialogId dialog_id, vector &&administrators, - bool have_access); + bool have_access, bool from_database); void speculative_add_channel_participants(ChannelId channel_id, const vector &added_user_ids, UserId inviter_user_id, int32 date, bool by_me); @@ -224,19 +239,7 @@ class ContactsManager : public Actor { bool on_get_channel_error(ChannelId channel_id, const Status &status, const string &source); - void on_get_channel_participants_success(ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset, - int32 limit, int64 random_id, int32 total_count, - vector> &&participants); - - void on_get_channel_participants_fail(ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset, - int32 limit, int64 random_id); - - static Slice get_dialog_invite_link_hash(const string &invite_link); - - void on_get_chat_invite_link(ChatId chat_id, tl_object_ptr &&invite_link_ptr); - - void on_get_channel_invite_link(ChannelId channel_id, - tl_object_ptr &&invite_link_ptr); + void on_get_permanent_dialog_invite_link(DialogId dialog_id, const DialogInviteLink &invite_link); void on_get_dialog_invite_link_info(const string &invite_link, tl_object_ptr &&chat_invite_ptr, @@ -360,6 +363,8 @@ class ContactsManager : public Actor { void toggle_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, Promise &&promise); + void convert_channel_to_gigagroup(ChannelId channel_id, Promise &&promise); + void set_channel_description(ChannelId channel_id, const string &description, Promise &&promise); void set_channel_discussion_group(DialogId dialog_id, DialogId discussion_dialog_id, Promise &&promise); @@ -371,7 +376,7 @@ class ContactsManager : public Actor { void report_channel_spam(ChannelId channel_id, UserId user_id, const vector &message_ids, Promise &&promise); - void delete_channel(ChannelId channel_id, Promise &&promise); + void delete_dialog(DialogId dialog_id, Promise &&promise); void get_channel_statistics(DialogId dialog_id, bool is_dark, Promise> &&promise); @@ -384,19 +389,6 @@ class ContactsManager : public Actor { void load_statistics_graph(DialogId dialog_id, const string &token, int64 x, Promise> &&promise); - void add_chat_participant(ChatId chat_id, UserId user_id, int32 forward_limit, Promise &&promise); - - void add_channel_participant(ChannelId channel_id, UserId user_id, Promise &&promise, - DialogParticipantStatus old_status = DialogParticipantStatus::Left()); - - void add_channel_participants(ChannelId channel_id, const vector &user_ids, Promise &&promise); - - void change_chat_participant_status(ChatId chat_id, UserId user_id, DialogParticipantStatus status, - Promise &&promise); - - void change_channel_participant_status(ChannelId channel_id, UserId user_id, DialogParticipantStatus status, - Promise &&promise); - void can_transfer_ownership(Promise &&promise); static td_api::object_ptr get_can_transfer_ownership_result_object( @@ -404,18 +396,37 @@ class ContactsManager : public Actor { void transfer_dialog_ownership(DialogId dialog_id, UserId user_id, const string &password, Promise &&promise); - void export_chat_invite_link(ChatId chat_id, Promise &&promise); + void export_dialog_invite_link(DialogId dialog_id, int32 expire_date, int32 usage_limit, bool is_permanent, + Promise> &&promise); - void export_channel_invite_link(ChannelId channel_id, Promise &&promise); + void edit_dialog_invite_link(DialogId dialog_id, const string &link, int32 expire_date, int32 usage_limit, + Promise> &&promise); + + void get_dialog_invite_link(DialogId dialog_id, const string &invite_link, + Promise> &&promise); + + void get_dialog_invite_link_counts(DialogId dialog_id, + Promise> &&promise); + + void get_dialog_invite_links(DialogId dialog_id, UserId creator_user_id, bool is_revoked, int32 offset_date, + const string &offset_invite_link, int32 limit, + Promise> &&promise); + + void get_dialog_invite_link_users(DialogId dialog_id, const string &invite_link, + td_api::object_ptr offset_member, int32 limit, + Promise> &&promise); + + void revoke_dialog_invite_link(DialogId dialog_id, const string &link, + Promise> &&promise); + + void delete_revoked_dialog_invite_link(DialogId dialog_id, const string &invite_link, Promise &&promise); + + void delete_all_revoked_dialog_invite_links(DialogId dialog_id, UserId creator_user_id, Promise &&promise); void check_dialog_invite_link(const string &invite_link, Promise &&promise) const; void import_dialog_invite_link(const string &invite_link, Promise &&promise); - string get_chat_invite_link(ChatId chat_id) const; - - string get_channel_invite_link(ChannelId channel_id); - ChannelId migrate_chat_to_megagroup(ChatId chat_id, Promise &promise); vector get_created_public_dialogs(PublicDialogType type, Promise &&promise); @@ -426,7 +437,9 @@ class ContactsManager : public Actor { vector get_inactive_channels(Promise &&promise); - bool is_user_contact(UserId user_id) const; + void dismiss_suggested_action(SuggestedAction action, Promise &&promise); + + bool is_user_contact(UserId user_id, bool is_mutual = false) const; bool is_user_deleted(UserId user_id) const; @@ -501,29 +514,28 @@ class ContactsManager : public Actor { ChannelId get_channel_linked_channel_id(ChannelId channel_id); int32 get_channel_slow_mode_delay(ChannelId channel_id); - std::pair> search_among_users(const vector &user_ids, const string &query, int32 limit); + void add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit, Promise &&promise); - DialogParticipant get_chat_participant(ChatId chat_id, UserId user_id, bool force, Promise &&promise); + void add_dialog_participants(DialogId dialog_id, const vector &user_ids, Promise &&promise); - std::pair> search_chat_participants(ChatId chat_id, const string &query, int32 limit, - DialogParticipantsFilter filter, bool force, - Promise &&promise); + void set_dialog_participant_status(DialogId dialog_id, UserId user_id, + const tl_object_ptr &chat_member_status, + Promise &&promise); - DialogParticipant get_channel_participant(ChannelId channel_id, UserId user_id, int64 &random_id, bool force, - Promise &&promise); + void ban_dialog_participant(DialogId dialog_id, UserId user_id, int32 banned_until_date, bool revoke_messages, + Promise &&promise); - std::pair> get_channel_participants( - ChannelId channel_id, const tl_object_ptr &filter, - const string &additional_query, int32 offset, int32 limit, int32 additional_limit, int64 &random_id, - bool without_bot_info, bool force, Promise &&promise); + DialogParticipant get_dialog_participant(DialogId dialog_id, UserId user_id, int64 &random_id, bool force, + Promise &&promise); - void send_get_channel_participants_query(ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset, - int32 limit, int64 random_id, Promise &&promise); + void search_dialog_participants(DialogId dialog_id, const string &query, int32 limit, DialogParticipantsFilter filter, + bool without_bot_info, Promise &&promise); - DialogParticipant get_dialog_participant(ChannelId channel_id, - tl_object_ptr &&participant_ptr) const; + vector get_dialog_administrators(DialogId dialog_id, int left_tries, Promise &&promise); - vector get_dialog_administrators(DialogId chat_id, int left_tries, Promise &&promise); + void get_channel_participants(ChannelId channel_id, tl_object_ptr &&filter, + string additional_query, int32 offset, int32 limit, int32 additional_limit, + bool without_bot_info, Promise &&promise); int32 get_user_id_object(UserId user_id, const char *source) const; @@ -609,7 +621,7 @@ class ContactsManager : public Actor { std::unordered_map online_member_dialogs; // id -> time - static constexpr uint32 CACHE_VERSION = 3; + static constexpr uint32 CACHE_VERSION = 4; uint32 cache_version = 0; bool is_min_access_hash = true; @@ -623,6 +635,7 @@ class ContactsManager : public Actor { bool is_inline_bot = false; bool need_location_bot = false; bool is_scam = false; + bool is_fake = false; bool is_contact = false; bool is_mutual_contact = false; bool need_apply_min_photo = false; @@ -658,26 +671,6 @@ class ContactsManager : public Actor { void parse(ParserT &parser); }; - struct BotInfo { - int32 version = -1; - string description; - vector> commands; - bool is_changed = true; - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - - struct UserPhotos { - vector photos; - int32 count = -1; - int32 offset = -1; - bool getting_now = false; - }; - // do not forget to update drop_user_full and on_get_user_full struct UserFull { Photo photo; @@ -700,7 +693,9 @@ class ContactsManager : public Actor { double expires_at = 0.0; - bool is_expired() const; + bool is_expired() const { + return expires_at < Time::now(); + } template void store(StorerT &storer) const; @@ -763,7 +758,7 @@ class ContactsManager : public Actor { string description; - string invite_link; + DialogInviteLink invite_link; bool can_set_username = false; @@ -789,7 +784,7 @@ class ContactsManager : public Actor { int32 date = 0; int32 participant_count = 0; - static constexpr uint32 CACHE_VERSION = 6; + static constexpr uint32 CACHE_VERSION = 7; uint32 cache_version = 0; bool has_linked_channel = false; @@ -798,8 +793,10 @@ class ContactsManager : public Actor { bool is_slow_mode_enabled = false; bool is_megagroup = false; + bool is_gigagroup = false; bool is_verified = false; bool is_scam = false; + bool is_fake = false; bool is_title_changed = true; bool is_username_changed = true; @@ -839,7 +836,8 @@ class ContactsManager : public Actor { int32 administrator_count = 0; int32 restricted_count = 0; int32 banned_count = 0; - string invite_link; + + DialogInviteLink invite_link; uint32 speculative_version = 1; uint32 repair_request_version = 0; @@ -874,7 +872,10 @@ class ContactsManager : public Actor { bool need_save_to_database = true; // have new changes that need only to be saved to the database double expires_at = 0.0; - bool is_expired() const; + + bool is_expired() const { + return expires_at < Time::now(); + } template void store(StorerT &storer) const; @@ -895,6 +896,7 @@ class ContactsManager : public Actor { bool is_outbound = false; + bool is_ttl_changed = true; bool is_state_changed = true; bool is_changed = true; // have new changes that need to be sent to the client and database bool need_save_to_database = true; // have new changes that need only to be saved to the database @@ -911,6 +913,19 @@ class ContactsManager : public Actor { void parse(ParserT &parser); }; + struct BotInfo { + int32 version = -1; + string description; + vector> commands; + bool is_changed = true; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + struct InviteLinkInfo { // known dialog DialogId dialog_id; @@ -926,6 +941,13 @@ class ContactsManager : public Actor { bool is_megagroup = false; }; + struct UserPhotos { + vector photos; + int32 count = -1; + int32 offset = -1; + bool getting_now = false; + }; + struct DialogNearby { DialogId dialog_id; int32 distance; @@ -981,6 +1003,7 @@ class ContactsManager : public Actor { static constexpr int32 USER_FLAG_IS_SUPPORT = 1 << 23; static constexpr int32 USER_FLAG_IS_SCAM = 1 << 24; static constexpr int32 USER_FLAG_NEED_APPLY_MIN_PHOTO = 1 << 25; + static constexpr int32 USER_FLAG_IS_FAKE = 1 << 26; static constexpr int32 USER_FULL_FLAG_IS_BLOCKED = 1 << 0; static constexpr int32 USER_FULL_FLAG_HAS_ABOUT = 1 << 1; @@ -990,6 +1013,7 @@ class ContactsManager : public Actor { static constexpr int32 USER_FULL_FLAG_CAN_PIN_MESSAGE = 1 << 7; static constexpr int32 USER_FULL_FLAG_HAS_FOLDER_ID = 1 << 11; static constexpr int32 USER_FULL_FLAG_HAS_SCHEDULED_MESSAGES = 1 << 12; + static constexpr int32 USER_FULL_FLAG_HAS_MESSAGE_TTL = 1 << 14; static constexpr int32 CHAT_FLAG_USER_IS_CREATOR = 1 << 0; static constexpr int32 CHAT_FLAG_USER_WAS_KICKED = 1 << 1; @@ -1005,6 +1029,7 @@ class ContactsManager : public Actor { static constexpr int32 CHAT_FULL_FLAG_HAS_SCHEDULED_MESSAGES = 1 << 8; static constexpr int32 CHAT_FULL_FLAG_HAS_FOLDER_ID = 1 << 11; static constexpr int32 CHAT_FULL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 12; + static constexpr int32 CHAT_FULL_FLAG_HAS_MESSAGE_TTL = 1 << 14; static constexpr int32 CHANNEL_FLAG_USER_IS_CREATOR = 1 << 0; static constexpr int32 CHANNEL_FLAG_USER_HAS_LEFT = 1 << 2; @@ -1027,6 +1052,8 @@ class ContactsManager : public Actor { static constexpr int32 CHANNEL_FLAG_IS_SLOW_MODE_ENABLED = 1 << 22; static constexpr int32 CHANNEL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 23; static constexpr int32 CHANNEL_FLAG_IS_GROUP_CALL_NON_EMPTY = 1 << 24; + static constexpr int32 CHANNEL_FLAG_IS_FAKE = 1 << 25; + static constexpr int32 CHANNEL_FLAG_IS_GIGAGROUP = 1 << 26; static constexpr int32 CHANNEL_FULL_FLAG_HAS_PARTICIPANT_COUNT = 1 << 0; static constexpr int32 CHANNEL_FULL_FLAG_HAS_ADMINISTRATOR_COUNT = 1 << 1; @@ -1051,6 +1078,8 @@ class ContactsManager : public Actor { static constexpr int32 CHANNEL_FULL_FLAG_CAN_VIEW_STATISTICS = 1 << 20; static constexpr int32 CHANNEL_FULL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 21; static constexpr int32 CHANNEL_FULL_FLAG_IS_BLOCKED = 1 << 22; + static constexpr int32 CHANNEL_FULL_FLAG_HAS_EXPORTED_INVITE = 1 << 23; + static constexpr int32 CHANNEL_FULL_FLAG_HAS_MESSAGE_TTL = 1 << 24; static constexpr int32 CHAT_INVITE_FLAG_IS_CHANNEL = 1 << 0; static constexpr int32 CHAT_INVITE_FLAG_IS_BROADCAST = 1 << 1; @@ -1065,8 +1094,6 @@ class ContactsManager : public Actor { static constexpr int32 ACCOUNT_UPDATE_LAST_NAME = 1 << 1; static constexpr int32 ACCOUNT_UPDATE_ABOUT = 1 << 2; - static const CSlice INVITE_LINK_URLS[3]; - void memory_cleanup(bool full); static bool have_input_peer_user(const User *u, AccessRights access_rights); @@ -1194,7 +1221,7 @@ class ContactsManager : public Actor { void on_update_chat_full_participants(ChatFull *chat_full, ChatId chat_id, vector participants, int32 version, bool from_update); void on_update_chat_full_invite_link(ChatFull *chat_full, - tl_object_ptr &&invite_link_ptr); + tl_object_ptr &&invite_link); void on_update_channel_photo(Channel *c, ChannelId channel_id, tl_object_ptr &&chat_photo_ptr); @@ -1207,7 +1234,7 @@ class ContactsManager : public Actor { void on_update_channel_full_photo(ChannelFull *channel_full, ChannelId channel_id, Photo photo); void on_update_channel_full_invite_link(ChannelFull *channel_full, - tl_object_ptr &&invite_link_ptr); + tl_object_ptr &&invite_link); void on_update_channel_full_linked_channel_id(ChannelFull *channel_full, ChannelId channel_id, ChannelId linked_channel_id); void on_update_channel_full_location(ChannelFull *channel_full, ChannelId channel_id, const DialogLocation &location); @@ -1323,7 +1350,7 @@ class ContactsManager : public Actor { bool is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id); - bool is_user_contact(const User *u, UserId user_id) const; + bool is_user_contact(const User *u, UserId user_id, bool is_mutual) const; int32 get_user_was_online(const User *u, UserId user_id) const; @@ -1350,6 +1377,10 @@ class ContactsManager : public Actor { void on_clear_imported_contacts(vector &&contacts, vector contacts_unique_id, std::pair, vector> &&to_add, Promise &&promise); + void send_update_chat_member(DialogId dialog_id, UserId agent_user_id, int32 date, DialogInviteLink invite_link, + const DialogParticipant &old_dialog_participant, + const DialogParticipant &new_dialog_participant); + static vector> get_chats_nearby_object( const vector &dialogs_nearby); @@ -1368,15 +1399,36 @@ class ContactsManager : public Actor { static bool is_channel_public(const Channel *c); + void export_dialog_invite_link_impl(DialogId dialog_id, int32 expire_date, int32 usage_limit, bool is_permanent, + Promise> &&promise); + void remove_dialog_access_by_invite_link(DialogId dialog_id); - static bool is_valid_invite_link(const string &invite_link); + Status can_manage_dialog_invite_links(DialogId dialog_id, bool creator_only = false); - bool update_invite_link(string &invite_link, tl_object_ptr &&invite_link_ptr); + bool update_permanent_invite_link(DialogInviteLink &invite_link, DialogInviteLink new_invite_link); + + void add_chat_participant(ChatId chat_id, UserId user_id, int32 forward_limit, Promise &&promise); + + void add_channel_participant(ChannelId channel_id, UserId user_id, Promise &&promise, + DialogParticipantStatus old_status = DialogParticipantStatus::Left()); + + void add_channel_participants(ChannelId channel_id, const vector &user_ids, Promise &&promise); const DialogParticipant *get_chat_participant(ChatId chat_id, UserId user_id) const; - static const DialogParticipant *get_chat_participant(const ChatFull *chat_full, UserId user_id); + static const DialogParticipant *get_chat_full_participant(const ChatFull *chat_full, UserId user_id); + + std::pair> search_among_users(const vector &user_ids, const string &query, + int32 limit) const; + + DialogParticipants search_private_chat_participants(UserId my_user_id, UserId peer_user_id, const string &query, + int32 limit, DialogParticipantsFilter filter) const; + + DialogParticipant get_chat_participant(ChatId chat_id, UserId user_id, bool force, Promise &&promise); + + DialogParticipant get_channel_participant(ChannelId channel_id, UserId user_id, int64 &random_id, bool force, + Promise &&promise); static string get_dialog_administrators_database_key(DialogId dialog_id); @@ -1389,6 +1441,10 @@ class ContactsManager : public Actor { void reload_dialog_administrators(DialogId dialog_id, int32 hash, Promise &&promise); + void remove_dialog_suggested_action(SuggestedAction action); + + void on_dismiss_suggested_action(SuggestedAction action, Result &&result); + static td_api::object_ptr get_update_unknown_user_object(UserId user_id); td_api::object_ptr get_user_status_object(UserId user_id, const User *u) const; @@ -1429,7 +1485,28 @@ class ContactsManager : public Actor { void update_dialogs_for_discussion(DialogId dialog_id, bool is_suitable); - void delete_chat_participant(ChatId chat_id, UserId user_id, Promise &&promise); + void change_chat_participant_status(ChatId chat_id, UserId user_id, DialogParticipantStatus status, + Promise &&promise); + + void change_channel_participant_status(ChannelId channel_id, UserId user_id, DialogParticipantStatus status, + Promise &&promise); + + void delete_chat_participant(ChatId chat_id, UserId user_id, bool revoke_messages, Promise &&promise); + + void search_chat_participants(ChatId chat_id, const string &query, int32 limit, DialogParticipantsFilter filter, + Promise &&promise); + + void do_search_chat_participants(ChatId chat_id, const string &query, int32 limit, DialogParticipantsFilter filter, + Promise &&promise); + + void do_get_channel_participants(ChannelId channel_id, ChannelParticipantsFilter &&filter, string additional_query, + int32 offset, int32 limit, int32 additional_limit, + Promise &&promise); + + void on_get_channel_participants(ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset, int32 limit, + string additional_query, int32 additional_limit, + tl_object_ptr &&channel_participants, + Promise &&promise); void change_channel_participant_status_impl(ChannelId channel_id, UserId user_id, DialogParticipantStatus status, DialogParticipantStatus old_status, Promise &&promise); @@ -1444,6 +1521,10 @@ class ContactsManager : public Actor { tl_object_ptr input_check_password, Promise &&promise); + void delete_chat(ChatId chat_id, Promise &&promise); + + void delete_channel(ChannelId channel_id, Promise &&promise); + void get_channel_statistics_dc_id(DialogId dialog_id, bool for_full_statistics, Promise &&promise); void get_channel_statistics_dc_id_impl(ChannelId channel_id, bool for_full_statistics, Promise &&promise); @@ -1517,7 +1598,6 @@ class ContactsManager : public Actor { std::unordered_set invite_links; int32 accessible_before = 0; }; - std::unordered_map dialog_invite_links_; // in-memory cache for invite links std::unordered_map> invite_link_infos_; std::unordered_map dialog_access_by_invite_link_; @@ -1552,6 +1632,9 @@ class ContactsManager : public Actor { std::unordered_map, DialogIdHash> dialog_administrators_; + std::unordered_map, DialogIdHash> dialog_suggested_actions_; + std::unordered_map>, DialogIdHash> dismiss_suggested_action_queries_; + class UploadProfilePhotoCallback; std::shared_ptr upload_profile_photo_callback_; @@ -1573,7 +1656,6 @@ class ContactsManager : public Actor { std::unordered_map, vector>> imported_contacts_; std::unordered_map received_channel_participant_; - std::unordered_map>> received_channel_participants_; std::unordered_map, ChannelIdHash> cached_channel_participants_; diff --git a/td/telegram/DialogAction.cpp b/td/telegram/DialogAction.cpp index 0ad50a6e6..6f2ac1dfa 100644 --- a/td/telegram/DialogAction.cpp +++ b/td/telegram/DialogAction.cpp @@ -6,6 +6,8 @@ // #include "td/telegram/DialogAction.h" +#include "td/utils/misc.h" + namespace td { void DialogAction::init(Type type) { @@ -14,11 +16,8 @@ void DialogAction::init(Type type) { } void DialogAction::init(Type type, int32 progress) { - if (progress < 0 || progress > 100) { - progress = 0; - } type_ = type; - progress_ = progress; + progress_ = clamp(progress, 0, 100); } DialogAction::DialogAction(Type type, int32 progress) { @@ -140,6 +139,11 @@ DialogAction::DialogAction(tl_object_ptr &&acti case telegram_api::speakingInGroupCallAction::ID: init(Type::SpeakingInVoiceChat); break; + case telegram_api::sendMessageHistoryImportAction::ID: { + auto history_import_action = move_tl_object_as(action); + init(Type::ImportingMessages, history_import_action->progress_); + break; + } default: UNREACHABLE(); break; @@ -176,6 +180,8 @@ tl_object_ptr DialogAction::get_input_send_mess return make_tl_object(progress_); case Type::SpeakingInVoiceChat: return make_tl_object(); + case Type::ImportingMessages: + return make_tl_object(progress_); default: UNREACHABLE(); return nullptr; @@ -212,6 +218,8 @@ tl_object_ptr DialogAction::get_secret_input_send return make_tl_object(); case Type::SpeakingInVoiceChat: return make_tl_object(); + case Type::ImportingMessages: + return make_tl_object(); default: UNREACHABLE(); return nullptr; @@ -246,6 +254,7 @@ tl_object_ptr DialogAction::get_chat_action_object() const { return td_api::make_object(); case Type::UploadingVideoNote: return td_api::make_object(progress_); + case Type::ImportingMessages: case Type::SpeakingInVoiceChat: default: UNREACHABLE(); @@ -350,6 +359,13 @@ DialogAction DialogAction::get_speaking_action() { return DialogAction(Type::SpeakingInVoiceChat, 0); } +int32 DialogAction::get_importing_messages_action_progress() const { + if (type_ != Type::ImportingMessages) { + return -1; + } + return progress_; +} + StringBuilder &operator<<(StringBuilder &string_builder, const DialogAction &action) { string_builder << "ChatAction"; const char *type = [action_type = action.type_] { @@ -382,6 +398,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DialogAction &act return "UploadingVideoNote"; case DialogAction::Type::SpeakingInVoiceChat: return "SpeakingInVoiceChat"; + case DialogAction::Type::ImportingMessages: + return "ImportingMessages"; default: UNREACHABLE(); return "Cancel"; diff --git a/td/telegram/DialogAction.h b/td/telegram/DialogAction.h index 0d457a7d7..cd33d248a 100644 --- a/td/telegram/DialogAction.h +++ b/td/telegram/DialogAction.h @@ -31,7 +31,8 @@ class DialogAction { StartPlayingGame, RecordingVideoNote, UploadingVideoNote, - SpeakingInVoiceChat + SpeakingInVoiceChat, + ImportingMessages }; Type type_ = Type::Cancel; int32 progress_ = 0; @@ -63,6 +64,8 @@ class DialogAction { static DialogAction get_speaking_action(); + int32 get_importing_messages_action_progress() const; + friend bool operator==(const DialogAction &lhs, const DialogAction &rhs) { return lhs.type_ == rhs.type_ && lhs.progress_ == rhs.progress_; } diff --git a/td/telegram/DialogAdministrator.h b/td/telegram/DialogAdministrator.h index 4a6a51e77..a46eeb116 100644 --- a/td/telegram/DialogAdministrator.h +++ b/td/telegram/DialogAdministrator.h @@ -22,7 +22,7 @@ class DialogAdministrator { string rank_; bool is_creator_ = false; - friend StringBuilder &operator<<(StringBuilder &string_builder, const DialogAdministrator &location); + friend StringBuilder &operator<<(StringBuilder &string_builder, const DialogAdministrator &administrator); public: DialogAdministrator() = default; diff --git a/td/telegram/DialogInviteLink.cpp b/td/telegram/DialogInviteLink.cpp new file mode 100644 index 000000000..565425b72 --- /dev/null +++ b/td/telegram/DialogInviteLink.cpp @@ -0,0 +1,134 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/DialogInviteLink.h" + +#include "td/telegram/ContactsManager.h" + +#include "td/utils/logging.h" +#include "td/utils/misc.h" + +namespace td { + +const CSlice DialogInviteLink::INVITE_LINK_URLS[12] = { + "t.me/joinchat/", "telegram.me/joinchat/", "telegram.dog/joinchat/", + "t.me/+", "telegram.me/+", "telegram.dog/+", + "t.me/ ", "telegram.me/ ", "telegram.dog/ ", + "t.me/%20", "telegram.me/%20", "telegram.dog/%20"}; + +DialogInviteLink::DialogInviteLink(tl_object_ptr exported_invite) { + if (exported_invite == nullptr) { + return; + } + + invite_link_ = std::move(exported_invite->link_); + LOG_IF(ERROR, !is_valid_invite_link(invite_link_)) << "Unsupported invite link " << invite_link_; + creator_user_id_ = UserId(exported_invite->admin_id_); + if (!creator_user_id_.is_valid()) { + LOG(ERROR) << "Receive invalid " << creator_user_id_ << " as creator of a link " << invite_link_; + creator_user_id_ = UserId(); + } + date_ = exported_invite->date_; + if (date_ < 1000000000) { + LOG(ERROR) << "Receive wrong date " << date_ << " as a creation date of a link " << invite_link_; + date_ = 0; + } + if ((exported_invite->flags_ & telegram_api::chatInviteExported::EXPIRE_DATE_MASK) != 0) { + expire_date_ = exported_invite->expire_date_; + if (expire_date_ < 1000000000) { + LOG(ERROR) << "Receive wrong date " << expire_date_ << " as an expire date of a link " << invite_link_; + expire_date_ = 0; + } + } + if ((exported_invite->flags_ & telegram_api::chatInviteExported::USAGE_LIMIT_MASK) != 0) { + usage_limit_ = exported_invite->usage_limit_; + if (usage_limit_ < 0) { + LOG(ERROR) << "Receive wrong usage limit " << usage_limit_ << " for a link " << invite_link_; + usage_limit_ = 0; + } + } + if ((exported_invite->flags_ & telegram_api::chatInviteExported::USAGE_MASK) != 0) { + usage_count_ = exported_invite->usage_; + if (usage_count_ < 0) { + LOG(ERROR) << "Receive wrong usage count " << usage_count_ << " for a link " << invite_link_; + usage_count_ = 0; + } + } + if ((exported_invite->flags_ & telegram_api::chatInviteExported::START_DATE_MASK) != 0) { + edit_date_ = exported_invite->start_date_; + if (edit_date_ < 1000000000) { + LOG(ERROR) << "Receive wrong date " << edit_date_ << " as an edit date of a link " << invite_link_; + edit_date_ = 0; + } + } + is_revoked_ = exported_invite->revoked_; + is_permanent_ = exported_invite->permanent_; + + if (is_permanent_ && (usage_limit_ > 0 || expire_date_ > 0 || edit_date_ > 0)) { + LOG(ERROR) << "Receive wrong permanent " << *this; + expire_date_ = 0; + usage_limit_ = 0; + edit_date_ = 0; + } +} + +bool DialogInviteLink::is_valid_invite_link(Slice invite_link) { + return !get_dialog_invite_link_hash(invite_link).empty(); +} + +Slice DialogInviteLink::get_dialog_invite_link_hash(Slice invite_link) { + auto lower_cased_invite_link_str = to_lower(invite_link); + Slice lower_cased_invite_link = lower_cased_invite_link_str; + size_t offset = 0; + if (begins_with(lower_cased_invite_link, "https://")) { + offset = 8; + } else if (begins_with(lower_cased_invite_link, "http://")) { + offset = 7; + } + lower_cased_invite_link.remove_prefix(offset); + + for (auto &url : INVITE_LINK_URLS) { + if (begins_with(lower_cased_invite_link, url)) { + Slice hash = invite_link.substr(url.size() + offset); + hash.truncate(hash.find('#')); + hash.truncate(hash.find('?')); + return hash; + } + } + return Slice(); +} + +td_api::object_ptr DialogInviteLink::get_chat_invite_link_object( + const ContactsManager *contacts_manager) const { + CHECK(contacts_manager != nullptr); + if (!is_valid()) { + return nullptr; + } + + return td_api::make_object( + invite_link_, contacts_manager->get_user_id_object(creator_user_id_, "get_chat_invite_link_object"), date_, + edit_date_, expire_date_, usage_limit_, usage_count_, is_permanent_, is_revoked_); +} + +bool operator==(const DialogInviteLink &lhs, const DialogInviteLink &rhs) { + return lhs.invite_link_ == rhs.invite_link_ && lhs.creator_user_id_ == rhs.creator_user_id_ && + lhs.date_ == rhs.date_ && lhs.edit_date_ == rhs.edit_date_ && lhs.expire_date_ == rhs.expire_date_ && + lhs.usage_limit_ == rhs.usage_limit_ && lhs.usage_count_ == rhs.usage_count_ && + lhs.is_permanent_ == rhs.is_permanent_ && lhs.is_revoked_ == rhs.is_revoked_; +} + +bool operator!=(const DialogInviteLink &lhs, const DialogInviteLink &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const DialogInviteLink &invite_link) { + return string_builder << "ChatInviteLink[" << invite_link.invite_link_ << " by " << invite_link.creator_user_id_ + << " created at " << invite_link.date_ << " edited at " << invite_link.edit_date_ + << " expiring at " << invite_link.expire_date_ << " used by " << invite_link.usage_count_ + << " with usage limit " << invite_link.usage_limit_ << "]"; +} + +} // namespace td diff --git a/td/telegram/DialogInviteLink.h b/td/telegram/DialogInviteLink.h new file mode 100644 index 000000000..b09fedb75 --- /dev/null +++ b/td/telegram/DialogInviteLink.h @@ -0,0 +1,137 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" + +#include "td/utils/common.h" +#include "td/utils/Slice.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +class ContactsManager; + +class DialogInviteLink { + string invite_link_; + UserId creator_user_id_; + int32 date_ = 0; + int32 edit_date_ = 0; + int32 expire_date_ = 0; + int32 usage_limit_ = 0; + int32 usage_count_ = 0; + bool is_revoked_ = false; + bool is_permanent_ = false; + + friend bool operator==(const DialogInviteLink &lhs, const DialogInviteLink &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const DialogInviteLink &invite_link); + + static const CSlice INVITE_LINK_URLS[12]; + + public: + DialogInviteLink() = default; + + explicit DialogInviteLink(tl_object_ptr exported_invite); + + static bool is_valid_invite_link(Slice invite_link); + + static Slice get_dialog_invite_link_hash(Slice invite_link); + + td_api::object_ptr get_chat_invite_link_object(const ContactsManager *contacts_manager) const; + + bool is_valid() const { + return !invite_link_.empty() && creator_user_id_.is_valid() && date_ > 0; + } + + bool is_permanent() const { + return is_permanent_; + } + + const string &get_invite_link() const { + return invite_link_; + } + + UserId get_creator_user_id() const { + return creator_user_id_; + } + + template + void store(StorerT &storer) const { + using td::store; + bool has_expire_date = expire_date_ != 0; + bool has_usage_limit = usage_limit_ != 0; + bool has_usage_count = usage_count_ != 0; + bool has_edit_date = edit_date_ != 0; + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_revoked_); + STORE_FLAG(is_permanent_); + STORE_FLAG(has_expire_date); + STORE_FLAG(has_usage_limit); + STORE_FLAG(has_usage_count); + STORE_FLAG(has_edit_date); + END_STORE_FLAGS(); + store(invite_link_, storer); + store(creator_user_id_, storer); + store(date_, storer); + if (has_expire_date) { + store(expire_date_, storer); + } + if (has_usage_limit) { + store(usage_limit_, storer); + } + if (has_usage_count) { + store(usage_count_, storer); + } + if (has_edit_date) { + store(edit_date_, storer); + } + } + + template + void parse(ParserT &parser) { + using td::parse; + bool has_expire_date; + bool has_usage_limit; + bool has_usage_count; + bool has_edit_date; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_revoked_); + PARSE_FLAG(is_permanent_); + PARSE_FLAG(has_expire_date); + PARSE_FLAG(has_usage_limit); + PARSE_FLAG(has_usage_count); + PARSE_FLAG(has_edit_date); + END_PARSE_FLAGS(); + parse(invite_link_, parser); + parse(creator_user_id_, parser); + parse(date_, parser); + if (has_expire_date) { + parse(expire_date_, parser); + } + if (has_usage_limit) { + parse(usage_limit_, parser); + } + if (has_usage_count) { + parse(usage_count_, parser); + } + if (has_edit_date) { + parse(edit_date_, parser); + } + } +}; + +bool operator==(const DialogInviteLink &lhs, const DialogInviteLink &rhs); + +bool operator!=(const DialogInviteLink &lhs, const DialogInviteLink &rhs); + +StringBuilder &operator<<(StringBuilder &string_builder, const DialogInviteLink &invite_link); + +} // namespace td diff --git a/td/telegram/DialogParticipant.cpp b/td/telegram/DialogParticipant.cpp index 5c7abce7c..339ea3a37 100644 --- a/td/telegram/DialogParticipant.cpp +++ b/td/telegram/DialogParticipant.cpp @@ -6,8 +6,10 @@ // #include "td/telegram/DialogParticipant.h" +#include "td/telegram/ContactsManager.h" #include "td/telegram/Global.h" #include "td/telegram/misc.h" +#include "td/telegram/Td.h" #include "td/utils/common.h" #include "td/utils/logging.h" @@ -35,12 +37,13 @@ DialogParticipantStatus DialogParticipantStatus::Creator(bool is_member, bool is } DialogParticipantStatus DialogParticipantStatus::Administrator(bool is_anonymous, string rank, bool can_be_edited, - bool can_change_info, bool can_post_messages, - bool can_edit_messages, bool can_delete_messages, - bool can_invite_users, bool can_restrict_members, - bool can_pin_messages, bool can_promote_members, - bool can_manage_calls) { + bool can_manage_dialog, bool can_change_info, + bool can_post_messages, bool can_edit_messages, + bool can_delete_messages, bool can_invite_users, + bool can_restrict_members, bool can_pin_messages, + bool can_promote_members, bool can_manage_calls) { uint32 flags = (static_cast(can_be_edited) * CAN_BE_EDITED) | + (static_cast(can_manage_dialog) * CAN_MANAGE_DIALOG) | (static_cast(can_change_info) * CAN_CHANGE_INFO_AND_SETTINGS_ADMIN) | (static_cast(can_post_messages) * CAN_POST_MESSAGES) | (static_cast(can_edit_messages) * CAN_EDIT_MESSAGES) | @@ -54,6 +57,7 @@ DialogParticipantStatus DialogParticipantStatus::Administrator(bool is_anonymous if (flags == 0 || flags == CAN_BE_EDITED) { return Member(); } + flags |= CAN_MANAGE_DIALOG; return DialogParticipantStatus(Type::Administrator, IS_MEMBER | ALL_RESTRICTED_RIGHTS | flags, 0, std::move(rank)); } @@ -92,14 +96,14 @@ DialogParticipantStatus DialogParticipantStatus::Banned(int32 banned_until_date) } DialogParticipantStatus DialogParticipantStatus::GroupAdministrator(bool is_creator) { - return Administrator(false, string(), is_creator, true, false, false, true, true, true, true, false, true); + return Administrator(false, string(), is_creator, true, true, false, false, true, true, true, true, false, true); } DialogParticipantStatus DialogParticipantStatus::ChannelAdministrator(bool is_creator, bool is_megagroup) { if (is_megagroup) { - return Administrator(false, string(), is_creator, true, false, false, true, true, true, true, false, false); + return Administrator(false, string(), is_creator, true, true, false, false, true, true, true, true, false, false); } else { - return Administrator(false, string(), is_creator, false, true, true, true, false, true, false, false, false); + return Administrator(false, string(), is_creator, true, false, true, true, true, false, true, false, false, false); } } @@ -115,9 +119,9 @@ tl_object_ptr DialogParticipantStatus::get_chat_member return td_api::make_object(rank_, is_anonymous(), is_member()); case Type::Administrator: return td_api::make_object( - rank_, can_be_edited(), can_change_info_and_settings(), can_post_messages(), can_edit_messages(), - can_delete_messages(), can_invite_users(), can_restrict_members(), can_pin_messages(), can_promote_members(), - can_manage_calls(), is_anonymous()); + rank_, can_be_edited(), can_manage_dialog(), can_change_info_and_settings(), can_post_messages(), + can_edit_messages(), can_delete_messages(), can_invite_users(), can_restrict_members(), can_pin_messages(), + can_promote_members(), can_manage_calls(), is_anonymous()); case Type::Member: return td_api::make_object(); case Type::Restricted: @@ -165,11 +169,14 @@ tl_object_ptr DialogParticipantStatus::get_chat_a if (is_anonymous()) { flags |= telegram_api::chatAdminRights::ANONYMOUS_MASK; } + if (can_manage_dialog()) { + flags |= telegram_api::chatAdminRights::OTHER_MASK; + } LOG(INFO) << "Create chat admin rights " << flags; return make_tl_object( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/); + false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/); } tl_object_ptr DialogParticipantStatus::get_chat_banned_rights() const { @@ -295,6 +302,9 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipant return string_builder; case DialogParticipantStatus::Type::Administrator: string_builder << "Administrator: "; + if (status.can_manage_dialog()) { + string_builder << "(manage)"; + } if (status.can_change_info_and_settings()) { string_builder << "(change)"; } @@ -402,7 +412,7 @@ DialogParticipantStatus get_dialog_participant_status(const tl_object_ptr(status.get()); return DialogParticipantStatus::Administrator( - st->is_anonymous_, st->custom_title_, true /*st->can_be_edited_*/, st->can_change_info_, + st->is_anonymous_, st->custom_title_, true /*st->can_be_edited_*/, st->can_manage_chat_, st->can_change_info_, st->can_post_messages_, st->can_edit_messages_, st->can_delete_messages_, st->can_invite_users_, st->can_restrict_members_, st->can_pin_messages_, st->can_promote_members_, st->can_manage_voice_chats_); } @@ -447,10 +457,14 @@ DialogParticipantStatus get_dialog_participant_status(bool can_be_edited, bool can_promote_members = (admin_rights->flags_ & telegram_api::chatAdminRights::ADD_ADMINS_MASK) != 0; bool can_manage_calls = (admin_rights->flags_ & telegram_api::chatAdminRights::MANAGE_CALL_MASK) != 0; bool is_anonymous = (admin_rights->flags_ & telegram_api::chatAdminRights::ANONYMOUS_MASK) != 0; - return DialogParticipantStatus::Administrator(is_anonymous, std::move(rank), can_be_edited, can_change_info, - can_post_messages, can_edit_messages, can_delete_messages, - can_invite_users, can_restrict_members, can_pin_messages, - can_promote_members, can_manage_calls); + bool can_manage_dialog = (admin_rights->flags_ & telegram_api::chatAdminRights::OTHER_MASK) != 0; + if (!can_manage_dialog) { + LOG(ERROR) << "Receive wrong other flag in " << to_string(admin_rights); + } + return DialogParticipantStatus::Administrator(is_anonymous, std::move(rank), can_be_edited, can_manage_dialog, + can_change_info, can_post_messages, can_edit_messages, + can_delete_messages, can_invite_users, can_restrict_members, + can_pin_messages, can_promote_members, can_manage_calls); } DialogParticipantStatus get_dialog_participant_status( @@ -641,8 +655,33 @@ DialogParticipant::DialogParticipant(UserId user_id, UserId inviter_user_id, int } } -DialogParticipant::DialogParticipant(tl_object_ptr &&participant_ptr, - DialogParticipantStatus my_status) { +DialogParticipant::DialogParticipant(tl_object_ptr &&participant_ptr, + int32 chat_creation_date, bool is_creator) { + switch (participant_ptr->get_id()) { + case telegram_api::chatParticipant::ID: { + auto participant = move_tl_object_as(participant_ptr); + *this = {UserId(participant->user_id_), UserId(participant->inviter_id_), participant->date_, + DialogParticipantStatus::Member()}; + break; + } + case telegram_api::chatParticipantCreator::ID: { + auto participant = move_tl_object_as(participant_ptr); + *this = {UserId(participant->user_id_), UserId(participant->user_id_), chat_creation_date, + DialogParticipantStatus::Creator(true, false, string())}; + break; + } + case telegram_api::chatParticipantAdmin::ID: { + auto participant = move_tl_object_as(participant_ptr); + *this = {UserId(participant->user_id_), UserId(participant->inviter_id_), participant->date_, + DialogParticipantStatus::GroupAdministrator(is_creator)}; + break; + } + default: + UNREACHABLE(); + } +} + +DialogParticipant::DialogParticipant(tl_object_ptr &&participant_ptr) { CHECK(participant_ptr != nullptr); switch (participant_ptr->get_id()) { case telegram_api::channelParticipant::ID: { @@ -653,7 +692,7 @@ DialogParticipant::DialogParticipant(tl_object_ptr(participant_ptr); *this = {UserId(participant->user_id_), UserId(participant->inviter_id_), participant->date_, - std::move(my_status)}; + DialogParticipantStatus::Member()}; break; } case telegram_api::channelParticipantCreator::ID: { @@ -705,6 +744,16 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipant << ']'; } +td_api::object_ptr DialogParticipants::get_chat_members_object(Td *td) const { + vector> chat_members; + chat_members.reserve(participants_.size()); + for (auto &participant : participants_) { + chat_members.push_back(td->contacts_manager_->get_chat_member_object(participant)); + } + + return td_api::make_object(total_count_, std::move(chat_members)); +} + tl_object_ptr ChannelParticipantsFilter::get_input_channel_participants_filter() const { switch (type) { @@ -721,7 +770,7 @@ ChannelParticipantsFilter::get_input_channel_participants_filter() const { if (!query.empty()) { flags |= telegram_api::channelParticipantsMentions::Q_MASK; } - if (!top_thread_message_id.is_valid()) { + if (top_thread_message_id.is_valid()) { flags |= telegram_api::channelParticipantsMentions::TOP_MSG_ID_MASK; } return make_tl_object( diff --git a/td/telegram/DialogParticipant.h b/td/telegram/DialogParticipant.h index ac0d96116..fb022a308 100644 --- a/td/telegram/DialogParticipant.h +++ b/td/telegram/DialogParticipant.h @@ -17,6 +17,8 @@ namespace td { +class Td; + class RestrictedRights { static constexpr uint32 CAN_SEND_MESSAGES = 1 << 16; static constexpr uint32 CAN_SEND_MEDIA = 1 << 17; @@ -119,6 +121,7 @@ class DialogParticipantStatus { static constexpr uint32 CAN_PIN_MESSAGES_ADMIN = 1 << 7; static constexpr uint32 CAN_PROMOTE_MEMBERS = 1 << 8; static constexpr uint32 CAN_MANAGE_CALLS = 1 << 9; + static constexpr uint32 CAN_MANAGE_DIALOG = 1 << 10; static constexpr uint32 CAN_BE_EDITED = 1 << 15; @@ -142,9 +145,10 @@ class DialogParticipantStatus { static constexpr int TYPE_SHIFT = 28; static constexpr uint32 HAS_UNTIL_DATE = 1u << 31; - static constexpr uint32 ALL_ADMINISTRATOR_RIGHTS = - CAN_CHANGE_INFO_AND_SETTINGS_ADMIN | CAN_POST_MESSAGES | CAN_EDIT_MESSAGES | CAN_DELETE_MESSAGES | - CAN_INVITE_USERS_ADMIN | CAN_RESTRICT_MEMBERS | CAN_PIN_MESSAGES_ADMIN | CAN_PROMOTE_MEMBERS | CAN_MANAGE_CALLS; + static constexpr uint32 ALL_ADMINISTRATOR_RIGHTS = CAN_CHANGE_INFO_AND_SETTINGS_ADMIN | CAN_POST_MESSAGES | + CAN_EDIT_MESSAGES | CAN_DELETE_MESSAGES | CAN_INVITE_USERS_ADMIN | + CAN_RESTRICT_MEMBERS | CAN_PIN_MESSAGES_ADMIN | + CAN_PROMOTE_MEMBERS | CAN_MANAGE_CALLS | CAN_MANAGE_DIALOG; static constexpr uint32 ALL_ADMIN_PERMISSION_RIGHTS = CAN_CHANGE_INFO_AND_SETTINGS_BANNED | CAN_INVITE_USERS_BANNED | CAN_PIN_MESSAGES_BANNED; @@ -169,9 +173,10 @@ class DialogParticipantStatus { public: static DialogParticipantStatus Creator(bool is_member, bool is_anonymous, string rank); - static DialogParticipantStatus Administrator(bool is_anonymous, string rank, bool can_be_edited, bool can_change_info, - bool can_post_messages, bool can_edit_messages, bool can_delete_messages, - bool can_invite_users, bool can_restrict_members, bool can_pin_messages, + static DialogParticipantStatus Administrator(bool is_anonymous, string rank, bool can_be_edited, + bool can_manage_dialog, bool can_change_info, bool can_post_messages, + bool can_edit_messages, bool can_delete_messages, bool can_invite_users, + bool can_restrict_members, bool can_pin_messages, bool can_promote_members, bool can_manage_calls); static DialogParticipantStatus Member(); @@ -206,6 +211,10 @@ class DialogParticipantStatus { // unrestricts user if restriction time expired. Should be called before all privileges checks void update_restrictions() const; + bool can_manage_dialog() const { + return (flags_ & CAN_MANAGE_DIALOG) != 0; + } + bool can_change_info_and_settings() const { return (flags_ & CAN_CHANGE_INFO_AND_SETTINGS_ADMIN) != 0 || (flags_ & CAN_CHANGE_INFO_AND_SETTINGS_BANNED) != 0; } @@ -357,6 +366,8 @@ class DialogParticipantStatus { if (is_creator()) { flags_ |= ALL_ADMINISTRATOR_RIGHTS | ALL_PERMISSION_RIGHTS; + } else if (is_administrator()) { + flags_ |= CAN_MANAGE_DIALOG; } } @@ -381,8 +392,10 @@ struct DialogParticipant { DialogParticipant(UserId user_id, UserId inviter_user_id, int32 joined_date, DialogParticipantStatus status); - DialogParticipant(tl_object_ptr &&participant_ptr, - DialogParticipantStatus my_status); + DialogParticipant(tl_object_ptr &&participant_ptr, int32 chat_creation_date, + bool is_creator); + + explicit DialogParticipant(tl_object_ptr &&participant_ptr); static DialogParticipant left(UserId user_id) { return {user_id, UserId(), 0, DialogParticipantStatus::Left()}; @@ -409,6 +422,18 @@ struct DialogParticipant { StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipant &dialog_participant); +struct DialogParticipants { + int32 total_count_ = 0; + vector participants_; + + DialogParticipants() = default; + DialogParticipants(int32 total_count, vector &&participants) + : total_count_(total_count), participants_(std::move(participants)) { + } + + td_api::object_ptr get_chat_members_object(Td *td) const; +}; + class ChannelParticipantsFilter { enum class Type : int32 { Recent, Contacts, Administrators, Search, Mention, Restricted, Banned, Bots } type; string query; diff --git a/td/telegram/DocumentsManager.cpp b/td/telegram/DocumentsManager.cpp index 3a9a90435..3001b46bc 100644 --- a/td/telegram/DocumentsManager.cpp +++ b/td/telegram/DocumentsManager.cpp @@ -82,7 +82,7 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo switch (attribute->get_id()) { case telegram_api::documentAttributeImageSize::ID: { auto image_size = move_tl_object_as(attribute); - dimensions = get_dimensions(image_size->w_, image_size->h_); + dimensions = get_dimensions(image_size->w_, image_size->h_, "documentAttributeImageSize"); break; } case telegram_api::documentAttributeAnimated::ID: @@ -114,8 +114,12 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo int32 video_duration = 0; if (video != nullptr) { video_duration = video->duration_; - if (dimensions.width == 0) { - dimensions = get_dimensions(video->w_, video->h_); + auto video_dimensions = get_dimensions(video->w_, video->h_, "documentAttributeVideo"); + if (dimensions.width == 0 || (video_dimensions.width != 0 && video_dimensions != dimensions)) { + if (dimensions.width != 0) { + LOG(ERROR) << "Receive ambiguous video dimensions " << dimensions << " and " << video_dimensions; + } + dimensions = video_dimensions; } if (animated != nullptr) { @@ -167,9 +171,6 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo default_extension = Slice("webp"); owner_dialog_id = DialogId(); file_name.clear(); - if (td_->stickers_manager_->has_webp_thumbnail(sticker) && remote_document.secret_file == nullptr) { - thumbnail_format = PhotoFormat::Webp; - } } else if (video != nullptr || default_document_type == Document::Type::Video || default_document_type == Document::Type::VideoNote) { bool is_video_note = default_document_type == Document::Type::VideoNote; @@ -252,6 +253,9 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo mime_type = std::move(document->mime_type_); file_reference = document->file_reference_.as_slice().str(); + if (document_type == Document::Type::Sticker && StickersManager::has_webp_thumbnail(document->thumbs_)) { + thumbnail_format = PhotoFormat::Webp; + } fix_animated_sticker_type(); if (owner_dialog_id.get_type() == DialogType::SecretChat) { @@ -265,9 +269,9 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo if (document_type != Document::Type::VoiceNote) { for (auto &thumb : document->thumbs_) { - auto photo_size = get_photo_size(td_->file_manager_.get(), {FileType::Thumbnail, 0}, id, access_hash, - file_reference, DcId::create(dc_id), owner_dialog_id, std::move(thumb), - thumbnail_format, document_type != Document::Type::Sticker); + auto photo_size = + get_photo_size(td_->file_manager_.get(), {FileType::Thumbnail, 0}, id, access_hash, file_reference, + DcId::create(dc_id), owner_dialog_id, std::move(thumb), thumbnail_format); if (photo_size.get_offset() == 0) { if (!thumbnail.file_id.is_valid()) { thumbnail = std::move(photo_size.get<0>()); @@ -435,6 +439,9 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo std::move(mime_type), !is_web); break; case Document::Type::Sticker: + if (thumbnail_format == PhotoFormat::Jpeg) { + minithumbnail = string(); + } td_->stickers_manager_->create_sticker(file_id, std::move(minithumbnail), std::move(thumbnail), dimensions, std::move(sticker), is_animated_sticker, load_data_multipromise_ptr); break; diff --git a/td/telegram/Global.cpp b/td/telegram/Global.cpp index 24b99b6fe..d52193afc 100644 --- a/td/telegram/Global.cpp +++ b/td/telegram/Global.cpp @@ -215,7 +215,7 @@ DcId Global::get_webfile_dc_id() const { return DcId::internal(dc_id); } -bool Global::ignore_backgrond_updates() const { +bool Global::ignore_background_updates() const { return !parameters_.use_file_db && !parameters_.use_secret_chats && shared_config_->get_option_boolean("ignore_background_updates"); } diff --git a/td/telegram/Global.h b/td/telegram/Global.h index b93967d59..38707f426 100644 --- a/td/telegram/Global.h +++ b/td/telegram/Global.h @@ -101,7 +101,7 @@ class Global : public ActorContext { return parameters_.use_test_dc; } - bool ignore_backgrond_updates() const; + bool ignore_background_updates() const; NetQueryCreator &net_query_creator() { return *net_query_creator_.get(); diff --git a/td/telegram/GroupCallManager.cpp b/td/telegram/GroupCallManager.cpp index 592c3a96c..e092f64eb 100644 --- a/td/telegram/GroupCallManager.cpp +++ b/td/telegram/GroupCallManager.cpp @@ -11,7 +11,6 @@ #include "td/telegram/ConfigManager.h" #include "td/telegram/ConfigShared.h" #include "td/telegram/ContactsManager.h" -#include "td/telegram/DialogParticipant.h" #include "td/telegram/Global.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessagesManager.h" @@ -124,11 +123,11 @@ class GetGroupCallParticipantQuery : public Td::ResultHandler { explicit GetGroupCallParticipantQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(InputGroupCallId input_group_call_id, vector user_ids, vector sources) { + void send(InputGroupCallId input_group_call_id, vector user_ids, vector audio_sources) { input_group_call_id_ = input_group_call_id; - auto limit = narrow_cast(max(user_ids.size(), sources.size())); + auto limit = narrow_cast(max(user_ids.size(), audio_sources.size())); send_query(G()->net_query_creator().create(telegram_api::phone_getGroupParticipants( - input_group_call_id.get_input_group_call(), std::move(user_ids), std::move(sources), string(), limit))); + input_group_call_id.get_input_group_call(), std::move(user_ids), std::move(audio_sources), string(), limit))); } void on_result(uint64 id, BufferSlice packet) override { @@ -212,8 +211,10 @@ class JoinGroupCallQuery : public Td::ResultHandler { return on_error(id, result_ptr.move_as_error()); } - td->group_call_manager_->process_join_group_call_response(input_group_call_id_, generation_, - result_ptr.move_as_ok(), std::move(promise_)); + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for JoinGroupCallQuery with generation " << generation_ << ": " << to_string(ptr); + td->group_call_manager_->process_join_group_call_response(input_group_call_id_, generation_, std::move(ptr), + std::move(promise_)); } void on_error(uint64 id, Status status) override { @@ -245,6 +246,10 @@ class ToggleGroupCallSettingsQuery : public Td::ResultHandler { } void on_error(uint64 id, Status status) override { + if (status.message() == "GROUPCALL_NOT_MODIFIED") { + promise_.set_value(Unit()); + return; + } promise_.set_error(std::move(status)); } }; @@ -284,17 +289,19 @@ class EditGroupCallMemberQuery : public Td::ResultHandler { explicit EditGroupCallMemberQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(InputGroupCallId input_group_call_id, UserId user_id, bool is_muted) { + void send(InputGroupCallId input_group_call_id, UserId user_id, bool is_muted, int32 volume_level) { auto input_user = td->contacts_manager_->get_input_user(user_id); CHECK(input_user != nullptr); int32 flags = 0; - if (is_muted) { + if (volume_level) { + flags |= telegram_api::phone_editGroupCallMember::VOLUME_MASK; + } else if (is_muted) { flags |= telegram_api::phone_editGroupCallMember::MUTED_MASK; } send_query(G()->net_query_creator().create(telegram_api::phone_editGroupCallMember( - flags, false /*ignored*/, input_group_call_id.get_input_group_call(), std::move(input_user)))); + flags, false /*ignored*/, input_group_call_id.get_input_group_call(), std::move(input_user), volume_level))); } void on_result(uint64 id, BufferSlice packet) override { @@ -320,9 +327,9 @@ class CheckGroupCallQuery : public Td::ResultHandler { explicit CheckGroupCallQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(InputGroupCallId input_group_call_id, int32 source) { + void send(InputGroupCallId input_group_call_id, int32 audio_source) { send_query(G()->net_query_creator().create( - telegram_api::phone_checkGroupCall(input_group_call_id.get_input_group_call(), source))); + telegram_api::phone_checkGroupCall(input_group_call_id.get_input_group_call(), audio_source))); } void on_result(uint64 id, BufferSlice packet) override { @@ -353,9 +360,9 @@ class LeaveGroupCallQuery : public Td::ResultHandler { explicit LeaveGroupCallQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(InputGroupCallId input_group_call_id, int32 source) { + void send(InputGroupCallId input_group_call_id, int32 audio_source) { send_query(G()->net_query_creator().create( - telegram_api::phone_leaveGroupCall(input_group_call_id.get_input_group_call(), source))); + telegram_api::phone_leaveGroupCall(input_group_call_id.get_input_group_call(), audio_source))); } void on_result(uint64 id, BufferSlice packet) override { @@ -420,8 +427,12 @@ struct GroupCallManager::GroupCall { int32 participant_count = 0; int32 version = -1; int32 duration = 0; - int32 source = 0; + int32 audio_source = 0; int32 joined_date = 0; + vector> after_join; + + bool have_pending_mute_new_participants = false; + bool pending_mute_new_participants = false; }; struct GroupCallManager::GroupCallParticipants { @@ -432,8 +443,8 @@ struct GroupCallManager::GroupCallParticipants { bool are_administrators_loaded = false; vector administrator_user_ids; - std::map>> pending_version_updates_; - std::map>> pending_mute_updates_; + std::map> pending_version_updates_; + std::map> pending_mute_updates_; }; struct GroupCallManager::GroupCallRecentSpeakers { @@ -445,7 +456,7 @@ struct GroupCallManager::GroupCallRecentSpeakers { struct GroupCallManager::PendingJoinRequest { NetQueryRef query_ref; uint64 generation = 0; - int32 source = 0; + int32 audio_source = 0; Promise> promise; }; @@ -527,21 +538,22 @@ void GroupCallManager::on_check_group_call_is_joined_timeout(GroupCallId group_c if(!(group_call != nullptr && group_call->is_inited)) { return; } - if (!group_call->is_joined || check_group_call_is_joined_timeout_.has_timeout(group_call_id.get())) { + if (!group_call->is_joined || pending_join_requests_.count(input_group_call_id) != 0 || + check_group_call_is_joined_timeout_.has_timeout(group_call_id.get())) { return; } - auto source = group_call->source; - auto promise = - PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, source](Result &&result) mutable { + auto audio_source = group_call->audio_source; + auto promise = PromiseCreator::lambda( + [actor_id = actor_id(this), input_group_call_id, audio_source](Result &&result) mutable { if (result.is_error() && result.error().message() == "GROUPCALL_JOIN_MISSING") { - send_closure(actor_id, &GroupCallManager::on_group_call_left, input_group_call_id, source, true); + send_closure(actor_id, &GroupCallManager::on_group_call_left, input_group_call_id, audio_source, true); result = Unit(); } - send_closure(actor_id, &GroupCallManager::finish_check_group_call_is_joined, input_group_call_id, source, + send_closure(actor_id, &GroupCallManager::finish_check_group_call_is_joined, input_group_call_id, audio_source, std::move(result)); }); - td_->create_handler(std::move(promise))->send(input_group_call_id, source); + td_->create_handler(std::move(promise))->send(input_group_call_id, audio_source); } void GroupCallManager::on_pending_send_speaking_action_timeout_callback(void *group_call_manager_ptr, @@ -786,7 +798,7 @@ void GroupCallManager::get_group_call(GroupCallId group_call_id, void GroupCallManager::on_update_group_call_rights(InputGroupCallId input_group_call_id) { auto group_call = get_group_call(input_group_call_id); - if (need_group_call_participants(input_group_call_id)) { + if (need_group_call_participants(input_group_call_id, group_call)) { if(!(group_call != nullptr && group_call->is_inited)) { return; } @@ -873,7 +885,7 @@ void GroupCallManager::finish_get_group_call(InputGroupCallId input_group_call_i } } -void GroupCallManager::finish_check_group_call_is_joined(InputGroupCallId input_group_call_id, int32 source, +void GroupCallManager::finish_check_group_call_is_joined(InputGroupCallId input_group_call_id, int32 audio_source, Result &&result) { LOG(INFO) << "Finish check group call is_joined for " << input_group_call_id; @@ -881,8 +893,9 @@ void GroupCallManager::finish_check_group_call_is_joined(InputGroupCallId input_ if(!(group_call != nullptr && group_call->is_inited)) { return; } - if (!group_call->is_joined || check_group_call_is_joined_timeout_.has_timeout(group_call->group_call_id.get()) || - group_call->source != source) { + if (!group_call->is_joined || pending_join_requests_.count(input_group_call_id) != 0 || + check_group_call_is_joined_timeout_.has_timeout(group_call->group_call_id.get()) || + group_call->audio_source != audio_source) { return; } @@ -890,15 +903,21 @@ void GroupCallManager::finish_check_group_call_is_joined(InputGroupCallId input_ check_group_call_is_joined_timeout_.set_timeout_in(group_call->group_call_id.get(), next_timeout); } +bool GroupCallManager::get_group_call_mute_new_participants(const GroupCall *group_call) { + return group_call->have_pending_mute_new_participants ? group_call->pending_mute_new_participants + : group_call->mute_new_participants; +} + bool GroupCallManager::need_group_call_participants(InputGroupCallId input_group_call_id) const { - auto *group_call = get_group_call(input_group_call_id); + return need_group_call_participants(input_group_call_id, get_group_call(input_group_call_id)); +} + +bool GroupCallManager::need_group_call_participants(InputGroupCallId input_group_call_id, + const GroupCall *group_call) const { if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) { return false; } - if (group_call->is_joined) { - return true; - } - if (pending_join_requests_.count(input_group_call_id) != 0) { + if (group_call->is_joined || group_call->need_rejoin || pending_join_requests_.count(input_group_call_id) != 0) { return true; } return false; @@ -1012,6 +1031,21 @@ GroupCallManager::GroupCallParticipants *GroupCallManager::add_group_call_partic return participants.get(); } +GroupCallParticipant *GroupCallManager::get_group_call_participant(InputGroupCallId input_group_call_id, + UserId user_id) { + return get_group_call_participant(add_group_call_participants(input_group_call_id), user_id); +} + +GroupCallParticipant *GroupCallManager::get_group_call_participant(GroupCallParticipants *group_call_participants, + UserId user_id) { + for (auto &group_call_participant : group_call_participants->participants) { + if (group_call_participant.user_id == user_id) { + return &group_call_participant; + } + } + return nullptr; +} + void GroupCallManager::on_update_group_call_participants( InputGroupCallId input_group_call_id, vector> &&participants, int32 version) { @@ -1023,7 +1057,7 @@ void GroupCallManager::on_update_group_call_participants( GroupCallParticipant participant(group_call_participant); if (participant.user_id == td_->contacts_manager_->get_my_id() && group_call != nullptr && group_call->is_inited && group_call->is_joined && - (participant.joined_date == 0) == (participant.source == group_call->source)) { + (participant.joined_date == 0) == (participant.audio_source == group_call->audio_source)) { on_group_call_left_impl(group_call, participant.joined_date == 0); need_update = true; } @@ -1069,9 +1103,23 @@ void GroupCallManager::on_update_group_call_participants( auto *group_call_participants = add_group_call_participants(input_group_call_id); auto &pending_mute_updates = group_call_participants->pending_mute_updates_[version]; - vector> version_updates; - for (auto &participant : participants) { - if (GroupCallParticipant::is_versioned_update(participant)) { + vector version_updates; + for (auto &group_call_participant : participants) { + GroupCallParticipant participant(group_call_participant); + if (participant.is_min && participant.joined_date != 0) { + auto old_participant = get_group_call_participant(group_call_participants, participant.user_id); + if (old_participant == nullptr) { + LOG(INFO) << "Can't apply min update about " << participant.user_id << " in " << input_group_call_id; + // TODO instead of synchronization, such participants can be received through GetGroupCallParticipantQuery + on_receive_group_call_version(input_group_call_id, version, true); + return; + } + + participant.update_from(*old_participant); + CHECK(!participant.is_min); + } + + if (GroupCallParticipant::is_versioned_update(group_call_participant)) { version_updates.push_back(std::move(participant)); } else { pending_mute_updates.push_back(std::move(participant)); @@ -1116,11 +1164,14 @@ bool GroupCallManager::process_pending_group_call_participant_updates(InputGroup auto version = it->first; auto &participants = it->second; if (version <= group_call->version) { - for (auto &group_call_participant : participants) { - GroupCallParticipant participant(group_call_participant); + auto my_user_id = td_->contacts_manager_->get_my_id(); + auto my_participant = get_group_call_participant(participants_it->second.get(), my_user_id); + for (auto &participant : participants) { on_participant_speaking_in_group_call(input_group_call_id, participant); - if (participant.user_id == td_->contacts_manager_->get_my_id() && version == group_call->version && - participant.is_just_joined) { + if (participant.user_id == my_user_id && (my_participant == nullptr || my_participant->is_fake || + my_participant->joined_date < participant.joined_date || + (my_participant->joined_date <= participant.joined_date && + my_participant->audio_source != participant.audio_source))) { process_group_call_participant(input_group_call_id, std::move(participant)); } } @@ -1135,7 +1186,8 @@ bool GroupCallManager::process_pending_group_call_participant_updates(InputGroup for (auto &participant : participants) { GroupCallParticipant group_call_participant(participant); if (group_call_participant.user_id == td_->contacts_manager_->get_my_id() && group_call->is_joined && - (group_call_participant.joined_date == 0) == (group_call_participant.source == group_call->source)) { + (group_call_participant.joined_date == 0) == + (group_call_participant.audio_source == group_call->audio_source)) { is_left = true; if (group_call_participant.joined_date != 0) { need_rejoin = false; @@ -1159,8 +1211,7 @@ bool GroupCallManager::process_pending_group_call_participant_updates(InputGroup auto version = it->first; if (version <= group_call->version) { auto &participants = it->second; - for (auto &group_call_participant : participants) { - GroupCallParticipant participant(group_call_participant); + for (auto &participant : participants) { on_participant_speaking_in_group_call(input_group_call_id, participant); int mute_diff = process_group_call_participant(input_group_call_id, std::move(participant)); CHECK(mute_diff == 0); @@ -1193,6 +1244,7 @@ bool GroupCallManager::process_pending_group_call_participant_updates(InputGroup if (need_update) { send_update_group_call(group_call, "process_pending_group_call_participant_updates"); } + try_clear_group_call_participants(input_group_call_id); return need_update; } @@ -1240,6 +1292,17 @@ void GroupCallManager::on_sync_group_call_participants_failed(InputGroupCallId i sync_participants_timeout_.add_timeout_in(group_call->group_call_id.get(), 1.0); } +int64 GroupCallManager::get_real_participant_order(const GroupCallParticipant &participant, int64 min_order) const { + auto real_order = participant.get_real_order(); + if (real_order < min_order && participant.user_id == td_->contacts_manager_->get_my_id()) { + return min_order; + } + if (real_order >= min_order) { + return real_order; + } + return 0; +} + void GroupCallManager::process_group_call_participants( InputGroupCallId input_group_call_id, vector> &&participants, bool is_load, bool is_sync) { @@ -1274,6 +1337,10 @@ void GroupCallManager::process_group_call_participants( LOG(ERROR) << "Receive invalid " << to_string(participant); continue; } + if (group_call_participant.is_min) { + LOG(ERROR) << "Receive unexpected min " << to_string(participant); + continue; + } auto real_order = group_call_participant.get_real_order(); if (real_order > min_order) { @@ -1303,8 +1370,15 @@ void GroupCallManager::process_group_call_participants( // not synced user, needs to be deleted if (participant.order != 0) { CHECK(participant.order >= participants_it->second->min_order); - participant.order = 0; - send_update_group_call_participant(input_group_call_id, participant); + if (participant.user_id == td_->contacts_manager_->get_my_id()) { + if (participant.order != min_order) { + participant.order = min_order; + send_update_group_call_participant(input_group_call_id, participant); + } + } else { + participant.order = 0; + send_update_group_call_participant(input_group_call_id, participant); + } } participant_it = group_participants.erase(participant_it); } @@ -1323,9 +1397,9 @@ void GroupCallManager::process_group_call_participants( participants_it->second->min_order = min_order; for (auto &participant : participants_it->second->participants) { - auto real_order = participant.get_real_order(); + auto real_order = get_real_participant_order(participant, min_order); if (old_min_order > real_order && real_order >= min_order) { - CHECK(participant.order == 0); + CHECK(participant.order == 0 || participant.order == old_min_order); participant.order = real_order; send_update_group_call_participant(input_group_call_id, participant); } @@ -1372,10 +1446,12 @@ int GroupCallManager::process_group_call_participant(InputGroupCallId input_grou if (!(group_call != nullptr && group_call->is_inited)) { return 0; } - if (group_call->is_joined && group_call->is_active && participant.source == group_call->source && - participant.is_muted && group_call->can_self_unmute != participant.can_self_unmute) { - group_call->can_self_unmute = participant.can_self_unmute; - send_update_group_call(group_call, "process_group_call_participant"); + if (group_call->is_joined && group_call->is_active) { + auto can_self_unmute = !participant.get_is_muted_by_admin(); + if (can_self_unmute != group_call->can_self_unmute) { + group_call->can_self_unmute = can_self_unmute; + send_update_group_call(group_call, "process_group_call_participant"); + } } } @@ -1394,21 +1470,10 @@ int GroupCallManager::process_group_call_participant(InputGroupCallId input_grou return -1; } - if (participant.joined_date < old_participant.joined_date) { - LOG(ERROR) << "Join date of " << participant.user_id << " in " << input_group_call_id << " decreased from " - << old_participant.joined_date << " to " << participant.joined_date; - participant.joined_date = old_participant.joined_date; - } - if (participant.active_date < old_participant.active_date) { - participant.active_date = old_participant.active_date; - } - participant.local_active_date = old_participant.local_active_date; - participant.is_speaking = old_participant.is_speaking; - auto real_order = participant.get_real_order(); - if (real_order >= participants->min_order) { - participant.order = real_order; - } + participant.update_from(old_participant); + participant.is_just_joined = false; + participant.order = get_real_participant_order(participant, participants->min_order); update_group_call_participant_can_be_muted(can_manage, participants, participant); LOG(INFO) << "Edit " << old_participant << " to " << participant; @@ -1427,16 +1492,14 @@ int GroupCallManager::process_group_call_participant(InputGroupCallId input_grou return -1; } + CHECK(!participant.is_min); int diff = participant.is_just_joined ? 1 : 0; if (participant.is_just_joined) { LOG(INFO) << "Add new " << participant; } else { LOG(INFO) << "Receive new " << participant; } - auto real_order = participant.get_real_order(); - if (real_order >= participants->min_order) { - participant.order = real_order; - } + participant.order = get_real_participant_order(participant, participants->min_order); participant.is_just_joined = false; update_group_call_participant_can_be_muted(can_manage, participants, participant); participants->participants.push_back(std::move(participant)); @@ -1447,38 +1510,45 @@ int GroupCallManager::process_group_call_participant(InputGroupCallId input_grou return diff; } +int32 GroupCallManager::cancel_join_group_call_request(InputGroupCallId input_group_call_id) { + auto it = pending_join_requests_.find(input_group_call_id); + if (it == pending_join_requests_.end()) { + return 0; + } + + CHECK(it->second != nullptr); + if (!it->second->query_ref.empty()) { + cancel_query(it->second->query_ref); + } + it->second->promise.set_error(Status::Error(200, "Cancelled")); + auto audio_source = it->second->audio_source; + pending_join_requests_.erase(it); + return audio_source; +} + void GroupCallManager::join_group_call(GroupCallId group_call_id, - td_api::object_ptr &&payload, int32 source, + td_api::object_ptr &&payload, int32 audio_source, bool is_muted, Promise> &&promise) { TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id)); auto *group_call = get_group_call(input_group_call_id); CHECK(group_call != nullptr); - if (group_call->is_joined) { - CHECK(group_call->is_inited); - return promise.set_error(Status::Error(400, "Group call is already joined")); - } if (group_call->is_inited && !group_call->is_active) { return promise.set_error(Status::Error(400, "Group call is finished")); } + bool need_update = false; + bool is_rejoin = group_call->need_rejoin; if (group_call->need_rejoin) { group_call->need_rejoin = false; - send_update_group_call(group_call, "join_group_call"); - } - group_call->is_being_left = false; - - if (pending_join_requests_.count(input_group_call_id)) { - auto it = pending_join_requests_.find(input_group_call_id); - CHECK(it != pending_join_requests_.end()); - CHECK(it->second != nullptr); - if (!it->second->query_ref.empty()) { - cancel_query(it->second->query_ref); - } - it->second->promise.set_error(Status::Error(200, "Cancelled by another joinGroupCall request")); - pending_join_requests_.erase(it); + need_update = true; } + cancel_join_group_call_request(input_group_call_id); + + if (audio_source == 0) { + return promise.set_error(Status::Error(400, "Audio source must be non-zero")); + } if (payload == nullptr) { return promise.set_error(Status::Error(400, "Payload must be non-empty")); } @@ -1503,7 +1573,12 @@ void GroupCallManager::join_group_call(GroupCallId group_call_id, } } - auto json_payload = json_encode(json_object([&payload, source](auto &o) { + if (group_call->is_being_left) { + group_call->is_being_left = false; + need_update |= group_call->is_joined; + } + + auto json_payload = json_encode(json_object([&payload, audio_source](auto &o) { o("ufrag", payload->ufrag_); o("pwd", payload->pwd_); o("fingerprints", json_array(payload->fingerprints_, @@ -1514,14 +1589,14 @@ void GroupCallManager::join_group_call(GroupCallId group_call_id, o("fingerprint", fingerprint->fingerprint_); }); })); - o("ssrc", source); + o("ssrc", audio_source); })); auto generation = ++join_group_request_generation_; auto &request = pending_join_requests_[input_group_call_id]; request = make_unique(); request->generation = generation; - request->source = source; + request->audio_source = audio_source; request->promise = std::move(promise); auto query_promise = @@ -1533,6 +1608,31 @@ void GroupCallManager::join_group_call(GroupCallId group_call_id, request->query_ref = td_->create_handler(std::move(query_promise)) ->send(input_group_call_id, json_payload, is_muted, generation); + if (group_call->is_inited && td_->contacts_manager_->have_user_force(td_->contacts_manager_->get_my_id())) { + GroupCallParticipant group_call_participant; + group_call_participant.user_id = td_->contacts_manager_->get_my_id(); + group_call_participant.audio_source = audio_source; + group_call_participant.joined_date = G()->unix_time(); + // if can_self_unmute has never been inited from self-participant, + // it contains reasonable default "!call.mute_new_participants || call.can_be_managed" + group_call_participant.server_is_muted_by_admin = + !group_call->can_self_unmute && !can_manage_group_call(input_group_call_id); + group_call_participant.server_is_muted_by_themselves = is_muted && !group_call_participant.server_is_muted_by_admin; + group_call_participant.is_just_joined = !is_rejoin; + group_call_participant.is_fake = true; + int diff = process_group_call_participant(input_group_call_id, std::move(group_call_participant)); + if (diff != 0) { + CHECK(diff == 1); + group_call->participant_count++; + need_update = true; + update_group_call_dialog(group_call, "join_group_call"); + } + } + + if (group_call->is_inited && need_update) { + send_update_group_call(group_call, "join_group_call"); + } + try_load_group_call_administrators(input_group_call_id, group_call->dialog_id); } @@ -1543,59 +1643,41 @@ void GroupCallManager::try_load_group_call_administrators(InputGroupCallId input return; } - unique_ptr random_id_ptr = td::make_unique(); - auto random_id_raw = random_id_ptr.get(); - auto promise = PromiseCreator::lambda( - [actor_id = actor_id(this), input_group_call_id, random_id = std::move(random_id_ptr)](Result &&result) { + auto promise = + PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id](Result &&result) { send_closure(actor_id, &GroupCallManager::finish_load_group_call_administrators, input_group_call_id, - *random_id, std::move(result)); + std::move(result)); }); - td_->messages_manager_->search_dialog_participants( - dialog_id, string(), 100, DialogParticipantsFilter(DialogParticipantsFilter::Type::Administrators), - *random_id_raw, true, true, std::move(promise)); + td_->contacts_manager_->search_dialog_participants( + dialog_id, string(), 100, DialogParticipantsFilter(DialogParticipantsFilter::Type::Administrators), true, + std::move(promise)); } -void GroupCallManager::finish_load_group_call_administrators(InputGroupCallId input_group_call_id, int64 random_id, - Result &&result) { +void GroupCallManager::finish_load_group_call_administrators(InputGroupCallId input_group_call_id, + Result &&result) { if (G()->close_flag()) { return; } + if (result.is_error()) { + LOG(WARNING) << "Failed to get administrators of " << input_group_call_id << ": " << result.error(); + return; + } auto *group_call = get_group_call(input_group_call_id); + if (!need_group_call_participants(input_group_call_id, group_call)) { + return; + } CHECK(group_call != nullptr); if (!group_call->dialog_id.is_valid() || !can_manage_group_calls(group_call->dialog_id).is_ok()) { return; } vector administrator_user_ids; - if (result.is_ok()) { - result = Status::Error(500, "Failed to receive result"); - unique_ptr ignore_result = make_unique(); - auto ignore_result_ptr = ignore_result.get(); - auto promise = PromiseCreator::lambda([&result, ignore_result = std::move(ignore_result)](Result new_result) { - if (!*ignore_result) { - result = std::move(new_result); - } - }); - auto participants = td_->messages_manager_->search_dialog_participants( - group_call->dialog_id, string(), 100, DialogParticipantsFilter(DialogParticipantsFilter::Type::Administrators), - random_id, true, true, std::move(promise)); - for (auto &administrator : participants.second) { - if (administrator.status.can_manage_calls() && administrator.user_id != td_->contacts_manager_->get_my_id()) { - administrator_user_ids.push_back(administrator.user_id); - } + auto participants = result.move_as_ok(); + for (auto &administrator : participants.participants_) { + if (administrator.status.can_manage_calls() && administrator.user_id != td_->contacts_manager_->get_my_id()) { + administrator_user_ids.push_back(administrator.user_id); } - - *ignore_result_ptr = true; - } - - if (result.is_error()) { - LOG(WARNING) << "Failed to get administrators of " << input_group_call_id << ": " << result.error(); - return; - } - - if (!need_group_call_participants(input_group_call_id)) { - return; } auto *group_call_participants = add_group_call_participants(input_group_call_id); @@ -1620,7 +1702,6 @@ void GroupCallManager::process_join_group_call_response(InputGroupCallId input_g return; } - LOG(INFO) << "Receive result for JoinGroupCallQuery: " << to_string(updates); td_->updates_manager_->on_get_updates(std::move(updates), PromiseCreator::lambda([promise = std::move(promise)](Unit) mutable { promise.set_error(Status::Error(500, "Wrong join response received")); @@ -1710,7 +1791,7 @@ bool GroupCallManager::on_join_group_call_response(InputGroupCallId input_group_ group_call->need_rejoin = false; group_call->is_being_left = false; group_call->joined_date = G()->unix_time(); - group_call->source = it->second->source; + group_call->audio_source = it->second->audio_source; it->second->promise.set_value(result.move_as_ok()); check_group_call_is_joined_timeout_.set_timeout_in(group_call->group_call_id.get(), CHECK_GROUP_CALL_IS_JOINED_TIMEOUT); @@ -1718,6 +1799,7 @@ bool GroupCallManager::on_join_group_call_response(InputGroupCallId input_group_ } pending_join_requests_.erase(it); try_clear_group_call_participants(input_group_call_id); + process_group_call_after_join_requests(input_group_call_id); return need_update; } @@ -1730,17 +1812,111 @@ void GroupCallManager::finish_join_group_call(InputGroupCallId input_group_call_ it->second->promise.set_error(std::move(error)); pending_join_requests_.erase(it); try_clear_group_call_participants(input_group_call_id); + process_group_call_after_join_requests(input_group_call_id); +} + +void GroupCallManager::process_group_call_after_join_requests(InputGroupCallId input_group_call_id) { + GroupCall *group_call = get_group_call(input_group_call_id); + if (group_call == nullptr || !group_call->is_inited) { + return; + } + if (pending_join_requests_.count(input_group_call_id) != 0 || group_call->need_rejoin) { + LOG(ERROR) << "Failed to process after-join requests: " << pending_join_requests_.count(input_group_call_id) << " " + << group_call->need_rejoin; + return; + } + if (group_call->after_join.empty()) { + return; + } + + auto promises = std::move(group_call->after_join); + reset_to_empty(group_call->after_join); + if (!group_call->is_active || !group_call->is_joined) { + for (auto &promise : promises) { + promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING")); + } + } else { + for (auto &promise : promises) { + promise.set_value(Unit()); + } + } } void GroupCallManager::toggle_group_call_mute_new_participants(GroupCallId group_call_id, bool mute_new_participants, Promise &&promise) { TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id)); + auto *group_call = get_group_call(input_group_call_id); + if (group_call == nullptr || !group_call->is_inited || !group_call->is_active || !group_call->can_be_managed || + !group_call->allowed_change_mute_new_participants) { + return promise.set_error(Status::Error(400, "Can't change mute_new_participant setting")); + } + + if (mute_new_participants == get_group_call_mute_new_participants(group_call)) { + return promise.set_value(Unit()); + } + + // there is no reason to save promise; we will send an update with actual value anyway + + group_call->pending_mute_new_participants = mute_new_participants; + if (!group_call->have_pending_mute_new_participants) { + group_call->have_pending_mute_new_participants = true; + send_toggle_group_call_mute_new_participants_query(input_group_call_id, mute_new_participants); + } + send_update_group_call(group_call, "toggle_group_call_mute_new_participants"); + promise.set_value(Unit()); +} + +void GroupCallManager::send_toggle_group_call_mute_new_participants_query(InputGroupCallId input_group_call_id, + bool mute_new_participants) { + auto promise = PromiseCreator::lambda( + [actor_id = actor_id(this), input_group_call_id, mute_new_participants](Result result) { + send_closure(actor_id, &GroupCallManager::on_toggle_group_call_mute_new_participants, input_group_call_id, + mute_new_participants, std::move(result)); + }); int32 flags = telegram_api::phone_toggleGroupCallSettings::JOIN_MUTED_MASK; td_->create_handler(std::move(promise)) ->send(flags, input_group_call_id, mute_new_participants); } +void GroupCallManager::on_toggle_group_call_mute_new_participants(InputGroupCallId input_group_call_id, + bool mute_new_participants, Result &&result) { + if (G()->close_flag()) { + return; + } + + auto *group_call = get_group_call(input_group_call_id); + if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) { + return; + } + + CHECK(group_call->have_pending_mute_new_participants); + if (result.is_error()) { + group_call->have_pending_mute_new_participants = false; + if (group_call->can_be_managed && group_call->allowed_change_mute_new_participants) { + LOG(ERROR) << "Failed to set mute_new_participants to " << mute_new_participants << " in " << input_group_call_id + << ": " << result.error(); + } + group_call->have_pending_mute_new_participants = false; + if (group_call->pending_mute_new_participants != group_call->mute_new_participants) { + send_update_group_call(group_call, "on_toggle_group_call_mute_new_participants failed"); + } + } else { + if (group_call->pending_mute_new_participants != mute_new_participants) { + // need to send another request + send_toggle_group_call_mute_new_participants_query(input_group_call_id, + group_call->pending_mute_new_participants); + return; + } + + group_call->have_pending_mute_new_participants = false; + if (group_call->mute_new_participants != mute_new_participants) { + LOG(ERROR) << "Failed to set mute_new_participants to " << mute_new_participants << " in " << input_group_call_id; + send_update_group_call(group_call, "on_toggle_group_call_mute_new_participants failed 2"); + } + } +} + void GroupCallManager::invite_group_call_participants(GroupCallId group_call_id, vector &&user_ids, Promise &&promise) { TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id)); @@ -1767,8 +1943,8 @@ void GroupCallManager::invite_group_call_participants(GroupCallId group_call_id, td_->create_handler(std::move(promise))->send(input_group_call_id, std::move(input_users)); } -void GroupCallManager::set_group_call_participant_is_speaking(GroupCallId group_call_id, int32 source, bool is_speaking, - Promise &&promise, int32 date) { +void GroupCallManager::set_group_call_participant_is_speaking(GroupCallId group_call_id, int32 audio_source, + bool is_speaking, Promise &&promise, int32 date) { if (G()->close_flag()) { return promise.set_error(Status::Error(500, "Request aborted")); } @@ -1776,11 +1952,27 @@ void GroupCallManager::set_group_call_participant_is_speaking(GroupCallId group_ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id)); auto *group_call = get_group_call(input_group_call_id); - if (group_call == nullptr || !group_call->is_inited || !group_call->is_active || !group_call->is_joined) { + if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) { return promise.set_value(Unit()); } - if (source == 0) { - source = group_call->source; + if (!group_call->is_joined) { + if (pending_join_requests_.count(input_group_call_id) || group_call->need_rejoin) { + group_call->after_join.push_back( + PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, audio_source, is_speaking, + promise = std::move(promise), date](Result &&result) mutable { + if (result.is_error()) { + promise.set_value(Unit()); + } else { + send_closure(actor_id, &GroupCallManager::set_group_call_participant_is_speaking, group_call_id, + audio_source, is_speaking, std::move(promise), date); + } + })); + return; + } + return promise.set_value(Unit()); + } + if (audio_source == 0) { + audio_source = group_call->audio_source; } bool recursive = false; @@ -1789,14 +1981,15 @@ void GroupCallManager::set_group_call_participant_is_speaking(GroupCallId group_ } else { recursive = true; } - if (source != group_call->source && !recursive && is_speaking && + if (audio_source != group_call->audio_source && !recursive && is_speaking && check_group_call_is_joined_timeout_.has_timeout(group_call_id.get())) { check_group_call_is_joined_timeout_.set_timeout_in(group_call_id.get(), CHECK_GROUP_CALL_IS_JOINED_TIMEOUT); } - UserId user_id = set_group_call_participant_is_speaking_by_source(input_group_call_id, source, is_speaking, date); + UserId user_id = + set_group_call_participant_is_speaking_by_source(input_group_call_id, audio_source, is_speaking, date); if (!user_id.is_valid()) { if (!recursive) { - auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, source, is_speaking, + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, audio_source, is_speaking, promise = std::move(promise), date](Result &&result) mutable { if (G()->close_flag()) { return promise.set_error(Status::Error(500, "Request aborted")); @@ -1804,14 +1997,14 @@ void GroupCallManager::set_group_call_participant_is_speaking(GroupCallId group_ if (result.is_error()) { promise.set_value(Unit()); } else { - send_closure(actor_id, &GroupCallManager::set_group_call_participant_is_speaking, group_call_id, source, + send_closure(actor_id, &GroupCallManager::set_group_call_participant_is_speaking, group_call_id, audio_source, is_speaking, std::move(promise), date); } }); td_->create_handler(std::move(query_promise)) - ->send(input_group_call_id, {}, {source}); + ->send(input_group_call_id, {}, {audio_source}); } else { - LOG(INFO) << "Failed to find participant with source " << source << " in " << group_call_id << " from " + LOG(INFO) << "Failed to find participant with source " << audio_source << " in " << group_call_id << " from " << group_call->dialog_id; promise.set_value(Unit()); } @@ -1822,12 +2015,11 @@ void GroupCallManager::set_group_call_participant_is_speaking(GroupCallId group_ on_user_speaking_in_group_call(group_call_id, user_id, date, recursive); } - if (group_call->source == source && group_call->dialog_id.is_valid() && group_call->is_speaking != is_speaking) { + if (group_call->audio_source == audio_source && group_call->dialog_id.is_valid() && + group_call->is_speaking != is_speaking) { group_call->is_speaking = is_speaking; if (is_speaking) { pending_send_speaking_action_timeout_.add_timeout_in(group_call_id.get(), 0.0); - } else { - pending_send_speaking_action_timeout_.cancel_timeout(group_call_id.get()); } } @@ -1839,22 +2031,185 @@ void GroupCallManager::toggle_group_call_participant_is_muted(GroupCallId group_ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id)); auto *group_call = get_group_call(input_group_call_id); - if (group_call == nullptr || !group_call->is_inited || !group_call->is_active || !group_call->is_joined) { + if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) { + return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING")); + } + if (!group_call->is_joined) { + if (pending_join_requests_.count(input_group_call_id) || group_call->need_rejoin) { + group_call->after_join.push_back( + PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, user_id, is_muted, + promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING")); + } else { + send_closure(actor_id, &GroupCallManager::toggle_group_call_participant_is_muted, group_call_id, user_id, + is_muted, std::move(promise)); + } + })); + return; + } return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING")); } if (!td_->contacts_manager_->have_input_user(user_id)) { return promise.set_error(Status::Error(400, "Have no access to the user")); } - if (user_id != td_->contacts_manager_->get_my_id()) { - TRY_STATUS_PROMISE(promise, can_manage_group_calls(group_call->dialog_id)); - } else { - if (!is_muted && !group_call->can_self_unmute) { - return promise.set_error(Status::Error(400, "Can't unmute self")); - } + auto participants = add_group_call_participants(input_group_call_id); + auto participant = get_group_call_participant(participants, user_id); + if (participant == nullptr) { + return promise.set_error(Status::Error(400, "Can't find group call participant")); } - td_->create_handler(std::move(promise))->send(input_group_call_id, user_id, is_muted); + bool is_self = user_id == td_->contacts_manager_->get_my_id(); + bool can_manage = can_manage_group_call(input_group_call_id); + bool is_admin = td::contains(participants->administrator_user_ids, user_id); + + auto participant_copy = *participant; + if (!participant_copy.set_pending_is_muted(is_muted, can_manage, is_self, is_admin)) { + return promise.set_error(Status::Error(400, PSLICE() << "Can't " << (is_muted ? "" : "un") << "mute user")); + } + if (participant_copy == *participant) { + return promise.set_value(Unit()); + } + *participant = std::move(participant_copy); + + participant->pending_is_muted_generation = ++toggle_is_muted_generation_; + if (participant->order != 0) { + send_update_group_call_participant(input_group_call_id, *participant); + } + + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, user_id, + generation = participant->pending_is_muted_generation, + promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &GroupCallManager::on_toggle_group_call_participant_is_muted, input_group_call_id, user_id, + generation, std::move(promise)); + }); + td_->create_handler(std::move(query_promise)) + ->send(input_group_call_id, user_id, is_muted, 0); +} + +void GroupCallManager::on_toggle_group_call_participant_is_muted(InputGroupCallId input_group_call_id, UserId user_id, + uint64 generation, Promise &&promise) { + if (G()->close_flag()) { + return promise.set_value(Unit()); + } + + auto *group_call = get_group_call(input_group_call_id); + if (group_call == nullptr || !group_call->is_inited || !group_call->is_active || !group_call->is_joined) { + return promise.set_value(Unit()); + } + + auto participant = get_group_call_participant(input_group_call_id, user_id); + if (participant == nullptr || participant->pending_is_muted_generation != generation) { + return promise.set_value(Unit()); + } + + CHECK(participant->have_pending_is_muted); + participant->have_pending_is_muted = false; + if (participant->server_is_muted_by_themselves != participant->pending_is_muted_by_themselves || + participant->server_is_muted_by_admin != participant->pending_is_muted_by_admin || + participant->server_is_muted_locally != participant->pending_is_muted_locally) { + LOG(ERROR) << "Failed to mute/unmute " << user_id << " in " << input_group_call_id; + if (participant->order != 0) { + send_update_group_call_participant(input_group_call_id, *participant); + } + } + promise.set_value(Unit()); +} + +void GroupCallManager::set_group_call_participant_volume_level(GroupCallId group_call_id, UserId user_id, + int32 volume_level, Promise &&promise) { + TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id)); + if (volume_level < GroupCallParticipant::MIN_VOLUME_LEVEL || volume_level > GroupCallParticipant::MAX_VOLUME_LEVEL) { + return promise.set_error(Status::Error(400, "Wrong volume level specified")); + } + + auto *group_call = get_group_call(input_group_call_id); + if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) { + return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING")); + } + if (!group_call->is_joined) { + if (pending_join_requests_.count(input_group_call_id) || group_call->need_rejoin) { + group_call->after_join.push_back( + PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, user_id, volume_level, + promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING")); + } else { + send_closure(actor_id, &GroupCallManager::set_group_call_participant_volume_level, group_call_id, user_id, + volume_level, std::move(promise)); + } + })); + return; + } + return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING")); + } + if (!td_->contacts_manager_->have_input_user(user_id)) { + return promise.set_error(Status::Error(400, "Have no access to the user")); + } + + if (user_id == td_->contacts_manager_->get_my_id()) { + return promise.set_error(Status::Error(400, "Can't change self volume level")); + } + + auto participant = get_group_call_participant(input_group_call_id, user_id); + if (participant == nullptr) { + return promise.set_error(Status::Error(400, "Can't find group call participant")); + } + + if (participant->get_volume_level() == volume_level) { + return promise.set_value(Unit()); + } + + participant->pending_volume_level = volume_level; + participant->pending_volume_level_generation = ++set_volume_level_generation_; + if (participant->order != 0) { + send_update_group_call_participant(input_group_call_id, *participant); + } + + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, user_id, + generation = participant->pending_volume_level_generation, + promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &GroupCallManager::on_set_group_call_participant_volume_level, input_group_call_id, user_id, + generation, std::move(promise)); + }); + td_->create_handler(std::move(query_promise)) + ->send(input_group_call_id, user_id, false, volume_level); +} + +void GroupCallManager::on_set_group_call_participant_volume_level(InputGroupCallId input_group_call_id, UserId user_id, + uint64 generation, Promise &&promise) { + if (G()->close_flag()) { + return promise.set_value(Unit()); + } + + auto *group_call = get_group_call(input_group_call_id); + if (group_call == nullptr || !group_call->is_inited || !group_call->is_active || !group_call->is_joined) { + return promise.set_value(Unit()); + } + + auto participant = get_group_call_participant(input_group_call_id, user_id); + if (participant == nullptr || participant->pending_volume_level_generation != generation) { + return promise.set_value(Unit()); + } + + CHECK(participant->pending_volume_level != 0); + if (participant->volume_level != participant->pending_volume_level) { + LOG(ERROR) << "Failed to set volume level of " << user_id << " in " << input_group_call_id; + participant->pending_volume_level = 0; + if (participant->order != 0) { + send_update_group_call_participant(input_group_call_id, *participant); + } + } else { + participant->pending_volume_level = 0; + } + promise.set_value(Unit()); } void GroupCallManager::load_group_call_participants(GroupCallId group_call_id, int32 limit, Promise &&promise) { @@ -1864,10 +2219,10 @@ void GroupCallManager::load_group_call_participants(GroupCallId group_call_id, i TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id)); - if (!need_group_call_participants(input_group_call_id)) { + auto *group_call = get_group_call(input_group_call_id); + if (!need_group_call_participants(input_group_call_id, group_call)) { return promise.set_error(Status::Error(400, "Can't load group call participants")); } - auto *group_call = get_group_call(input_group_call_id); if (!(group_call != nullptr && group_call->is_inited)) { return promise.set_error(Status::Error(400, "Internal error")); } @@ -1889,29 +2244,49 @@ void GroupCallManager::leave_group_call(GroupCallId group_call_id, Promise TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id)); auto *group_call = get_group_call(input_group_call_id); - if (group_call == nullptr || !group_call->is_inited || !group_call->is_active || !group_call->is_joined) { + if (group_call == nullptr || !group_call->is_inited || !group_call->is_active || !group_call->is_joined || + group_call->is_being_left) { + if (cancel_join_group_call_request(input_group_call_id) != 0) { + try_clear_group_call_participants(input_group_call_id); + process_group_call_after_join_requests(input_group_call_id); + return promise.set_value(Unit()); + } + if (group_call->need_rejoin) { + group_call->need_rejoin = false; + send_update_group_call(group_call, "leave_group_call"); + try_clear_group_call_participants(input_group_call_id); + process_group_call_after_join_requests(input_group_call_id); + return promise.set_value(Unit()); + } return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING")); } - auto source = group_call->source; + auto audio_source = cancel_join_group_call_request(input_group_call_id); + if (audio_source == 0) { + audio_source = group_call->audio_source; + } group_call->is_being_left = true; + group_call->need_rejoin = false; + send_update_group_call(group_call, "leave_group_call"); - auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, source, + process_group_call_after_join_requests(input_group_call_id); + + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, audio_source, promise = std::move(promise)](Result &&result) mutable { if (result.is_ok()) { // just in case - send_closure(actor_id, &GroupCallManager::on_group_call_left, input_group_call_id, source, false); + send_closure(actor_id, &GroupCallManager::on_group_call_left, input_group_call_id, audio_source, false); } promise.set_result(std::move(result)); }); - td_->create_handler(std::move(query_promise))->send(input_group_call_id, source); + td_->create_handler(std::move(query_promise))->send(input_group_call_id, audio_source); } -void GroupCallManager::on_group_call_left(InputGroupCallId input_group_call_id, int32 source, bool need_rejoin) { +void GroupCallManager::on_group_call_left(InputGroupCallId input_group_call_id, int32 audio_source, bool need_rejoin) { auto *group_call = get_group_call(input_group_call_id); if (!(group_call != nullptr && group_call->is_inited)) { return; } - if (group_call->is_joined && group_call->source == source) { + if (group_call->is_joined && group_call->audio_source == audio_source) { on_group_call_left_impl(group_call, need_rejoin); send_update_group_call(group_call, "on_group_call_left"); } @@ -1923,16 +2298,23 @@ void GroupCallManager::on_group_call_left_impl(GroupCall *group_call, bool need_ } group_call->is_joined = false; group_call->need_rejoin = need_rejoin && !group_call->is_being_left; + if (group_call->need_rejoin && group_call->dialog_id.is_valid()) { + auto dialog_id = group_call->dialog_id; + if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read) || + (dialog_id.get_type() == DialogType::Chat && + !td_->contacts_manager_->get_chat_status(dialog_id.get_chat_id()).is_member())) { + group_call->need_rejoin = false; + } + } group_call->is_being_left = false; group_call->is_speaking = false; - group_call->can_self_unmute = false; group_call->can_be_managed = false; group_call->joined_date = 0; - group_call->source = 0; - group_call->loaded_all_participants = false; - group_call->version = -1; + group_call->audio_source = 0; check_group_call_is_joined_timeout_.cancel_timeout(group_call->group_call_id.get()); - try_clear_group_call_participants(get_input_group_call_id(group_call->group_call_id).ok()); + auto input_group_call_id = get_input_group_call_id(group_call->group_call_id).ok(); + try_clear_group_call_participants(input_group_call_id); + process_group_call_after_join_requests(input_group_call_id); } void GroupCallManager::discard_group_call(GroupCallId group_call_id, Promise &&promise) { @@ -1942,7 +2324,6 @@ void GroupCallManager::discard_group_call(GroupCallId group_call_id, Promise group_call_ptr, DialogId dialog_id) { if (G()->shared_config().get_option_boolean("disable_group_calls") || td_->auth_manager_->is_bot()) { - LOG(ERROR) << "Receive " << to_string(group_call_ptr); return; } if (dialog_id != DialogId() && !dialog_id.is_valid()) { @@ -1958,11 +2339,14 @@ void GroupCallManager::on_update_group_call(tl_object_ptrcontacts_manager_->get_my_id()); + + auto participants_it = group_call_participants_.find(input_group_call_id); + if (participants_it == group_call_participants_.end()) { return; } @@ -2032,7 +2416,7 @@ InputGroupCallId GroupCallManager::update_group_call(const tl_object_ptrgroup_call_id; call.dialog_id = dialog_id.is_valid() ? dialog_id : group_call->dialog_id; call.can_be_managed = call.is_active && can_manage_group_calls(call.dialog_id).is_ok(); - call.can_self_unmute = (call.is_active && !call.mute_new_participants) || call.can_be_managed; + call.can_self_unmute = call.is_active && (!call.mute_new_participants || call.can_be_managed); if (!group_call->dialog_id.is_valid()) { group_call->dialog_id = dialog_id; } @@ -2043,15 +2427,14 @@ InputGroupCallId GroupCallManager::update_group_call(const tl_object_ptrneed_rejoin; call.is_being_left = group_call->is_being_left; call.is_speaking = group_call->is_speaking; - call.can_self_unmute = group_call->can_self_unmute; call.syncing_participants = group_call->syncing_participants; call.loaded_all_participants = group_call->loaded_all_participants; - call.source = group_call->source; + call.audio_source = group_call->audio_source; *group_call = std::move(call); - if (need_group_call_participants(input_group_call_id)) { + need_update = true; + if (need_group_call_participants(input_group_call_id, group_call)) { // init version - group_call->version = call.version; if (process_pending_group_call_participant_updates(input_group_call_id)) { need_update = false; } @@ -2059,12 +2442,15 @@ InputGroupCallId GroupCallManager::update_group_call(const tl_object_ptrversion = -1; } - need_update = true; } else { if (!group_call->is_active) { // never update ended calls } else if (!call.is_active) { - // always update to an ended call, droping also is_joined and is_speaking flags + // always update to an ended call, droping also is_joined, is_speaking and other local flags + auto promises = std::move(group_call->after_join); + for (auto &promise : promises) { + promise.set_error(Status::Error(400, "Group call ended")); + } *group_call = std::move(call); need_update = true; } else { @@ -2072,9 +2458,14 @@ InputGroupCallId GroupCallManager::update_group_call(const tl_object_ptrmute_new_participants || call.allowed_change_mute_new_participants != group_call->allowed_change_mute_new_participants; if (mute_flags_changed && call.version >= group_call->version) { + auto old_mute_new_participants = get_group_call_mute_new_participants(group_call); + need_update |= (call.allowed_change_mute_new_participants && call.can_be_managed) != + (group_call->allowed_change_mute_new_participants && group_call->can_be_managed); group_call->mute_new_participants = call.mute_new_participants; group_call->allowed_change_mute_new_participants = call.allowed_change_mute_new_participants; - need_update = true; + if (old_mute_new_participants != get_group_call_mute_new_participants(group_call)) { + need_update = true; + } } if (call.can_be_managed != group_call->can_be_managed) { group_call->can_be_managed = call.can_be_managed; @@ -2090,12 +2481,9 @@ InputGroupCallId GroupCallManager::update_group_call(const tl_object_ptrparticipant_count = call.participant_count; need_update = true; } - if (need_group_call_participants(input_group_call_id) && !join_params.empty()) { + if (need_group_call_participants(input_group_call_id, group_call) && !join_params.empty() && + group_call->version == -1) { LOG(INFO) << "Init " << call.group_call_id << " version to " << call.version; - if (group_call->can_self_unmute != call.can_self_unmute) { - group_call->can_self_unmute = call.can_self_unmute; - need_update = true; - } group_call->version = call.version; if (process_pending_group_call_participant_updates(input_group_call_id)) { need_update = false; @@ -2119,12 +2507,12 @@ InputGroupCallId GroupCallManager::update_group_call(const tl_object_ptris_inited)) { return; } @@ -2142,7 +2530,11 @@ void GroupCallManager::on_receive_group_call_version(InputGroupCallId input_grou LOG(INFO) << "Receive version " << version << " for group call " << input_group_call_id; auto *group_call_participants = add_group_call_participants(input_group_call_id); group_call_participants->pending_version_updates_[version]; // reserve place for updates - sync_participants_timeout_.add_timeout_in(group_call->group_call_id.get(), 1.0); + if (immediate_sync) { + sync_participants_timeout_.set_timeout_in(group_call->group_call_id.get(), 0.0); + } else { + sync_participants_timeout_.add_timeout_in(group_call->group_call_id.get(), 1.0); + } } void GroupCallManager::on_participant_speaking_in_group_call(InputGroupCallId input_group_call_id, @@ -2176,7 +2568,9 @@ void GroupCallManager::on_user_speaking_in_group_call(GroupCallId group_call_id, return; } - if (!td_->contacts_manager_->have_user_force(user_id)) { + if (!td_->contacts_manager_->have_user_force(user_id) || + (!recursive && need_group_call_participants(input_group_call_id, group_call) && + get_group_call_participant(input_group_call_id, user_id) == nullptr)) { if (recursive) { LOG(WARNING) << "Failed to find speaking " << user_id << " from " << input_group_call_id; } else { @@ -2205,17 +2599,11 @@ void GroupCallManager::on_user_speaking_in_group_call(GroupCallId group_call_id, return; } recent_speakers->users[i].second = date; - bool is_updated = false; while (i > 0 && recent_speakers->users[i - 1].second < date) { std::swap(recent_speakers->users[i - 1], recent_speakers->users[i]); i--; - is_updated = true; - } - if (is_updated) { - on_group_call_recent_speakers_updated(group_call, recent_speakers.get()); - } else { - LOG(INFO) << "Position of " << user_id << " in recent speakers list didn't change"; } + on_group_call_recent_speakers_updated(group_call, recent_speakers.get()); return; } } @@ -2274,23 +2662,21 @@ void GroupCallManager::on_group_call_recent_speakers_updated(const GroupCall *gr } UserId GroupCallManager::set_group_call_participant_is_speaking_by_source(InputGroupCallId input_group_call_id, - int32 source, bool is_speaking, int32 date) { + int32 audio_source, bool is_speaking, + int32 date) { auto participants_it = group_call_participants_.find(input_group_call_id); if (participants_it == group_call_participants_.end()) { return UserId(); } for (auto &participant : participants_it->second->participants) { - if (participant.source == source) { + if (participant.audio_source == audio_source) { if (participant.is_speaking != is_speaking) { participant.is_speaking = is_speaking; if (is_speaking) { participant.local_active_date = max(participant.local_active_date, date); } - auto real_order = participant.get_real_order(); - if (real_order >= participants_it->second->min_order) { - participant.order = real_order; - } + participant.order = get_real_participant_order(participant, participants_it->second->min_order); if (participant.order != 0) { send_update_group_call_participant(input_group_call_id, participant); } @@ -2333,7 +2719,7 @@ vector> GroupCallManager::get vector> recent_speaker_users; for (auto &recent_speaker : recent_speakers->users) { - recent_speaker_users.emplace_back(recent_speaker.first, recent_speaker.second > now - 5); + recent_speaker_users.emplace_back(recent_speaker.first, recent_speaker.second > now - 8); } if (recent_speakers->is_changed) { @@ -2371,11 +2757,15 @@ tl_object_ptr GroupCallManager::get_group_call_object( CHECK(group_call != nullptr); CHECK(group_call->is_inited); + bool is_joined = group_call->is_joined && !group_call->is_being_left; + bool can_self_unmute = is_joined && group_call->can_self_unmute; + bool mute_new_participants = get_group_call_mute_new_participants(group_call); + bool can_change_mute_new_participants = + group_call->is_active && group_call->can_be_managed && group_call->allowed_change_mute_new_participants; return td_api::make_object( - group_call->group_call_id.get(), group_call->is_active, group_call->is_joined, group_call->need_rejoin, - group_call->can_self_unmute, group_call->can_be_managed, group_call->participant_count, - group_call->loaded_all_participants, std::move(recent_speakers), group_call->mute_new_participants, - group_call->allowed_change_mute_new_participants, group_call->duration); + group_call->group_call_id.get(), group_call->is_active, is_joined, group_call->need_rejoin, can_self_unmute, + group_call->can_be_managed, group_call->participant_count, group_call->loaded_all_participants, + std::move(recent_speakers), mute_new_participants, can_change_mute_new_participants, group_call->duration); } tl_object_ptr GroupCallManager::get_update_group_call_object( diff --git a/td/telegram/GroupCallManager.h b/td/telegram/GroupCallManager.h index 02e4e85ff..40d919e91 100644 --- a/td/telegram/GroupCallManager.h +++ b/td/telegram/GroupCallManager.h @@ -7,6 +7,7 @@ #pragma once #include "td/telegram/DialogId.h" +#include "td/telegram/DialogParticipant.h" #include "td/telegram/GroupCallId.h" #include "td/telegram/GroupCallParticipant.h" #include "td/telegram/InputGroupCallId.h" @@ -51,20 +52,24 @@ class GroupCallManager : public Actor { void reload_group_call(InputGroupCallId input_group_call_id, Promise> &&promise); - void join_group_call(GroupCallId group_call_id, td_api::object_ptr &&payload, int32 source, - bool is_muted, Promise> &&promise); + void join_group_call(GroupCallId group_call_id, td_api::object_ptr &&payload, + int32 audio_source, bool is_muted, + Promise> &&promise); void toggle_group_call_mute_new_participants(GroupCallId group_call_id, bool mute_new_participants, Promise &&promise); void invite_group_call_participants(GroupCallId group_call_id, vector &&user_ids, Promise &&promise); - void set_group_call_participant_is_speaking(GroupCallId group_call_id, int32 source, bool is_speaking, + void set_group_call_participant_is_speaking(GroupCallId group_call_id, int32 audio_source, bool is_speaking, Promise &&promise, int32 date = 0); void toggle_group_call_participant_is_muted(GroupCallId group_call_id, UserId user_id, bool is_muted, Promise &&promise); + void set_group_call_participant_volume_level(GroupCallId group_call_id, UserId user_id, int32 volume_level, + Promise &&promise); + void load_group_call_participants(GroupCallId group_call_id, int32 limit, Promise &&promise); void leave_group_call(GroupCallId group_call_id, Promise &&promise); @@ -133,16 +138,23 @@ class GroupCallManager : public Actor { void finish_get_group_call(InputGroupCallId input_group_call_id, Result> &&result); - void finish_check_group_call_is_joined(InputGroupCallId input_group_call_id, int32 source, Result &&result); + void finish_check_group_call_is_joined(InputGroupCallId input_group_call_id, int32 audio_source, + Result &&result); + + static bool get_group_call_mute_new_participants(const GroupCall *group_call); bool need_group_call_participants(InputGroupCallId input_group_call_id) const; + bool need_group_call_participants(InputGroupCallId input_group_call_id, const GroupCall *group_call) const; + bool process_pending_group_call_participant_updates(InputGroupCallId input_group_call_id); void sync_group_call_participants(InputGroupCallId input_group_call_id); void on_sync_group_call_participants_failed(InputGroupCallId input_group_call_id); + int64 get_real_participant_order(const GroupCallParticipant &participant, int64 min_order) const; + void process_group_call_participants(InputGroupCallId group_call_id, vector> &&participants, bool is_load, bool is_sync); @@ -157,22 +169,42 @@ class GroupCallManager : public Actor { void try_load_group_call_administrators(InputGroupCallId input_group_call_id, DialogId dialog_id); - void finish_load_group_call_administrators(InputGroupCallId input_group_call_id, int64 random_id, - Result &&result); + void finish_load_group_call_administrators(InputGroupCallId input_group_call_id, Result &&result); + + int32 cancel_join_group_call_request(InputGroupCallId input_group_call_id); bool on_join_group_call_response(InputGroupCallId input_group_call_id, string json_response); void finish_join_group_call(InputGroupCallId input_group_call_id, uint64 generation, Status error); + void process_group_call_after_join_requests(InputGroupCallId input_group_call_id); + GroupCallParticipants *add_group_call_participants(InputGroupCallId input_group_call_id); - void on_group_call_left(InputGroupCallId input_group_call_id, int32 source, bool need_rejoin); + GroupCallParticipant *get_group_call_participant(InputGroupCallId input_group_call_id, UserId user_id); + + static GroupCallParticipant *get_group_call_participant(GroupCallParticipants *group_call_participants, + UserId user_id); + + void send_toggle_group_call_mute_new_participants_query(InputGroupCallId input_group_call_id, + bool mute_new_participants); + + void on_toggle_group_call_mute_new_participants(InputGroupCallId input_group_call_id, bool mute_new_participants, + Result &&result); + + void on_toggle_group_call_participant_is_muted(InputGroupCallId input_group_call_id, UserId user_id, + uint64 generation, Promise &&promise); + + void on_set_group_call_participant_volume_level(InputGroupCallId input_group_call_id, UserId user_id, + uint64 generation, Promise &&promise); + + void on_group_call_left(InputGroupCallId input_group_call_id, int32 audio_source, bool need_rejoin); void on_group_call_left_impl(GroupCall *group_call, bool need_rejoin); InputGroupCallId update_group_call(const tl_object_ptr &group_call_ptr, DialogId dialog_id); - void on_receive_group_call_version(InputGroupCallId input_group_call_id, int32 version); + void on_receive_group_call_version(InputGroupCallId input_group_call_id, int32 version, bool immediate_sync = false); void on_participant_speaking_in_group_call(InputGroupCallId input_group_call_id, const GroupCallParticipant &participant); @@ -181,7 +213,7 @@ class GroupCallManager : public Actor { void on_group_call_recent_speakers_updated(const GroupCall *group_call, GroupCallRecentSpeakers *recent_speakers); - UserId set_group_call_participant_is_speaking_by_source(InputGroupCallId input_group_call_id, int32 source, + UserId set_group_call_participant_is_speaking_by_source(InputGroupCallId input_group_call_id, int32 audio_source, bool is_speaking, int32 date); static Result> get_group_call_join_response_object( @@ -230,6 +262,10 @@ class GroupCallManager : public Actor { std::unordered_map, InputGroupCallIdHash> pending_join_requests_; uint64 join_group_request_generation_ = 0; + uint64 set_volume_level_generation_ = 0; + + uint64 toggle_is_muted_generation_ = 0; + MultiTimeout check_group_call_is_joined_timeout_{"CheckGroupCallIsJoinedTimeout"}; MultiTimeout pending_send_speaking_action_timeout_{"PendingSendSpeakingActionTimeout"}; MultiTimeout recent_speaker_update_timeout_{"RecentSpeakerUpdateTimeout"}; diff --git a/td/telegram/GroupCallParticipant.cpp b/td/telegram/GroupCallParticipant.cpp index c125d1646..6403f248e 100644 --- a/td/telegram/GroupCallParticipant.cpp +++ b/td/telegram/GroupCallParticipant.cpp @@ -15,9 +15,18 @@ namespace td { GroupCallParticipant::GroupCallParticipant(const tl_object_ptr &participant) { CHECK(participant != nullptr); user_id = UserId(participant->user_id_); - source = participant->source_; - is_muted = participant->muted_; - can_self_unmute = participant->can_self_unmute_; + audio_source = participant->source_; + server_is_muted_by_themselves = participant->can_self_unmute_; + server_is_muted_by_admin = participant->muted_ && !participant->can_self_unmute_; + server_is_muted_locally = participant->muted_by_you_; + if ((participant->flags_ & telegram_api::groupCallParticipant::VOLUME_MASK) != 0) { + volume_level = participant->volume_; + if (volume_level < MIN_VOLUME_LEVEL || volume_level > MAX_VOLUME_LEVEL) { + LOG(ERROR) << "Receive " << to_string(participant); + volume_level = 10000; + } + is_volume_level_local = (participant->flags_ & telegram_api::groupCallParticipant::VOLUME_BY_ADMIN_MASK) == 0; + } if (!participant->left_) { joined_date = participant->date_; if ((participant->flags_ & telegram_api::groupCallParticipant::ACTIVE_DATE_MASK) != 0) { @@ -30,38 +39,168 @@ GroupCallParticipant::GroupCallParticipant(const tl_object_ptrjust_joined_; + is_min = (participant->flags_ & telegram_api::groupCallParticipant::MIN_MASK) != 0; } bool GroupCallParticipant::is_versioned_update(const tl_object_ptr &participant) { return participant->just_joined_ || participant->left_ || participant->versioned_; } -bool GroupCallParticipant::update_can_be_muted(bool can_manage, bool is_self, bool is_admin) { - bool new_can_be_muted = false; - bool new_can_be_unmuted = false; - if (is_self) { - // current user can be muted if !is_muted; after that is_muted && can_self_unmute - // current user can be unmuted if is_muted && can_self_unmute; after that !is_muted - new_can_be_muted = !is_muted; - new_can_be_unmuted = is_muted && can_self_unmute; - } else if (is_admin) { - // admin user can be muted if can_manage && !is_muted; after that is_muted && can_self_unmute - // admin user can't be unmuted - new_can_be_muted = can_manage && !is_muted; - } else { - // other user can be muted if can_manage; after that is_muted && !can_self_unmute - // other user can be unmuted if can_manage && is_muted && !can_self_unmute; after that is_muted && can_self_unmute - new_can_be_muted = can_manage && (!is_muted || can_self_unmute); - new_can_be_unmuted = can_manage && is_muted && !can_self_unmute; +bool GroupCallParticipant::get_is_muted_by_themselves() const { + return have_pending_is_muted ? pending_is_muted_by_themselves : server_is_muted_by_themselves; +} + +bool GroupCallParticipant::get_is_muted_by_admin() const { + return have_pending_is_muted ? pending_is_muted_by_admin : server_is_muted_by_admin; +} + +bool GroupCallParticipant::get_is_muted_locally() const { + return have_pending_is_muted ? pending_is_muted_locally : server_is_muted_locally; +} + +bool GroupCallParticipant::get_is_muted_for_all_users() const { + return get_is_muted_by_admin() || get_is_muted_by_themselves(); +} + +int32 GroupCallParticipant::get_volume_level() const { + return pending_volume_level != 0 ? pending_volume_level : volume_level; +} + +void GroupCallParticipant::update_from(const GroupCallParticipant &old_participant) { + CHECK(!old_participant.is_min); + if (joined_date < old_participant.joined_date) { + LOG(ERROR) << "Join date decreased from " << old_participant.joined_date << " to " << joined_date; + joined_date = old_participant.joined_date; } - if (new_can_be_muted != can_be_muted || new_can_be_unmuted != can_be_unmuted) { - can_be_muted = new_can_be_muted; - can_be_unmuted = new_can_be_unmuted; + if (active_date < old_participant.active_date) { + active_date = old_participant.active_date; + } + local_active_date = old_participant.local_active_date; + is_speaking = old_participant.is_speaking; + if (is_min) { + server_is_muted_locally = old_participant.server_is_muted_locally; + + if (old_participant.is_volume_level_local && !is_volume_level_local) { + is_volume_level_local = true; + volume_level = old_participant.volume_level; + } + } + is_min = false; + + pending_volume_level = old_participant.pending_volume_level; + pending_volume_level_generation = old_participant.pending_volume_level_generation; + + have_pending_is_muted = old_participant.have_pending_is_muted; + pending_is_muted_by_themselves = old_participant.pending_is_muted_by_themselves; + pending_is_muted_by_admin = old_participant.pending_is_muted_by_admin; + pending_is_muted_locally = old_participant.pending_is_muted_locally; + pending_is_muted_generation = old_participant.pending_is_muted_generation; +} + +bool GroupCallParticipant::update_can_be_muted(bool can_manage, bool is_self, bool is_admin) { + bool is_muted_by_admin = get_is_muted_by_admin(); + bool is_muted_by_themselves = get_is_muted_by_themselves(); + bool is_muted_locally = get_is_muted_locally(); + + CHECK(!is_muted_by_admin || !is_muted_by_themselves); + + bool new_can_be_muted_for_all_users = false; + bool new_can_be_unmuted_for_all_users = false; + bool new_can_be_muted_only_for_self = !can_manage && !is_muted_locally; + bool new_can_be_unmuted_only_for_self = !can_manage && is_muted_locally; + if (is_self) { + // current user can be muted if !is_muted_by_themselves && !is_muted_by_admin; after that is_muted_by_themselves + // current user can be unmuted if is_muted_by_themselves; after that !is_muted + new_can_be_muted_for_all_users = !is_muted_by_themselves && !is_muted_by_admin; + new_can_be_unmuted_for_all_users = is_muted_by_themselves; + new_can_be_muted_only_for_self = false; + new_can_be_unmuted_only_for_self = false; + } else if (is_admin) { + // admin user can be muted if can_manage && !is_muted_by_themselves; after that is_muted_by_themselves + // admin user can't be unmuted + new_can_be_muted_for_all_users = can_manage && !is_muted_by_themselves; + } else { + // other users can be muted if can_manage && !is_muted_by_admin; after that is_muted_by_admin + // other users can be unmuted if can_manage && is_muted_by_admin; after that is_muted_by_themselves + new_can_be_muted_for_all_users = can_manage && !is_muted_by_admin; + new_can_be_unmuted_for_all_users = can_manage && is_muted_by_admin; + } + CHECK(static_cast(new_can_be_muted_for_all_users) + static_cast(new_can_be_unmuted_for_all_users) + + static_cast(new_can_be_muted_only_for_self) + static_cast(new_can_be_unmuted_only_for_self) <= + 1); + if (new_can_be_muted_for_all_users != can_be_muted_for_all_users || + new_can_be_unmuted_for_all_users != can_be_unmuted_for_all_users || + new_can_be_muted_only_for_self != can_be_muted_only_for_self || + new_can_be_unmuted_only_for_self != can_be_unmuted_only_for_self) { + can_be_muted_for_all_users = new_can_be_muted_for_all_users; + can_be_unmuted_for_all_users = new_can_be_unmuted_for_all_users; + can_be_muted_only_for_self = new_can_be_muted_only_for_self; + can_be_unmuted_only_for_self = new_can_be_unmuted_only_for_self; return true; } return false; } +bool GroupCallParticipant::set_pending_is_muted(bool is_muted, bool can_manage, bool is_self, bool is_admin) { + update_can_be_muted(can_manage, is_self, is_admin); + if (is_muted) { + if (!can_be_muted_for_all_users && !can_be_muted_only_for_self) { + return false; + } + CHECK(!can_be_muted_for_all_users || !can_be_muted_only_for_self); + } else { + if (!can_be_unmuted_for_all_users && !can_be_unmuted_only_for_self) { + return false; + } + CHECK(!can_be_unmuted_for_all_users || !can_be_unmuted_only_for_self); + } + + if (is_self) { + pending_is_muted_by_themselves = is_muted; + pending_is_muted_by_admin = false; + pending_is_muted_locally = false; + } else { + pending_is_muted_by_themselves = get_is_muted_by_themselves(); + pending_is_muted_by_admin = get_is_muted_by_admin(); + pending_is_muted_locally = get_is_muted_locally(); + if (is_muted) { + if (can_be_muted_only_for_self) { + // local mute + pending_is_muted_locally = true; + } else { + // admin mute + CHECK(can_be_muted_for_all_users); + CHECK(can_manage); + if (is_admin) { + CHECK(!pending_is_muted_by_themselves); + pending_is_muted_by_admin = false; + pending_is_muted_by_themselves = true; + } else { + CHECK(!pending_is_muted_by_admin); + pending_is_muted_by_admin = true; + pending_is_muted_by_themselves = false; + } + } + } else { + if (can_be_unmuted_only_for_self) { + // local unmute + pending_is_muted_locally = false; + } else { + // admin unmute + CHECK(can_be_unmuted_for_all_users); + CHECK(can_manage); + CHECK(!is_admin); + pending_is_muted_by_admin = false; + pending_is_muted_by_themselves = true; + } + } + } + + have_pending_is_muted = true; + update_can_be_muted(can_manage, is_self, is_admin); + return true; +} + td_api::object_ptr GroupCallParticipant::get_group_call_participant_object( ContactsManager *contacts_manager) const { if (!is_valid()) { @@ -69,14 +208,22 @@ td_api::object_ptr GroupCallParticipant::get_group } return td_api::make_object( - contacts_manager->get_user_id_object(user_id, "get_group_call_participant_object"), source, is_speaking, - can_be_muted, can_be_unmuted, is_muted, can_self_unmute, order); + contacts_manager->get_user_id_object(user_id, "get_group_call_participant_object"), audio_source, is_speaking, + can_be_muted_for_all_users, can_be_unmuted_for_all_users, can_be_muted_only_for_self, + can_be_unmuted_only_for_self, get_is_muted_for_all_users(), get_is_muted_locally(), get_is_muted_by_themselves(), + get_volume_level(), order); } bool operator==(const GroupCallParticipant &lhs, const GroupCallParticipant &rhs) { - return lhs.user_id == rhs.user_id && lhs.source == rhs.source && lhs.can_be_muted == rhs.can_be_muted && - lhs.can_be_unmuted == rhs.can_be_unmuted && lhs.is_muted == rhs.is_muted && - lhs.can_self_unmute == rhs.can_self_unmute && lhs.is_speaking == rhs.is_speaking && lhs.order == rhs.order; + return lhs.user_id == rhs.user_id && lhs.audio_source == rhs.audio_source && + lhs.can_be_muted_for_all_users == rhs.can_be_muted_for_all_users && + lhs.can_be_unmuted_for_all_users == rhs.can_be_unmuted_for_all_users && + lhs.can_be_muted_only_for_self == rhs.can_be_muted_only_for_self && + lhs.can_be_unmuted_only_for_self == rhs.can_be_unmuted_only_for_self && + lhs.get_is_muted_for_all_users() == rhs.get_is_muted_for_all_users() && + lhs.get_is_muted_locally() == rhs.get_is_muted_locally() && + lhs.get_is_muted_by_themselves() == rhs.get_is_muted_by_themselves() && lhs.is_speaking == rhs.is_speaking && + lhs.get_volume_level() == rhs.get_volume_level() && lhs.order == rhs.order; } bool operator!=(const GroupCallParticipant &lhs, const GroupCallParticipant &rhs) { @@ -84,8 +231,8 @@ bool operator!=(const GroupCallParticipant &lhs, const GroupCallParticipant &rhs } StringBuilder &operator<<(StringBuilder &string_builder, const GroupCallParticipant &group_call_participant) { - return string_builder << '[' << group_call_participant.user_id << " with source " << group_call_participant.source - << " and order " << group_call_participant.order << ']'; + return string_builder << '[' << group_call_participant.user_id << " with source " + << group_call_participant.audio_source << " and order " << group_call_participant.order << ']'; } } // namespace td diff --git a/td/telegram/GroupCallParticipant.h b/td/telegram/GroupCallParticipant.h index 9e9a05fc5..3d665d8d4 100644 --- a/td/telegram/GroupCallParticipant.h +++ b/td/telegram/GroupCallParticipant.h @@ -19,28 +19,51 @@ class ContactsManager; struct GroupCallParticipant { UserId user_id; - int32 source = 0; + int32 audio_source = 0; int32 joined_date = 0; int32 active_date = 0; - bool is_muted = false; - bool can_self_unmute = false; + int32 volume_level = 10000; + bool is_volume_level_local = false; + bool server_is_muted_by_themselves = false; + bool server_is_muted_by_admin = false; + bool server_is_muted_locally = false; - bool can_be_muted = false; - bool can_be_unmuted = false; + bool can_be_muted_for_all_users = false; + bool can_be_unmuted_for_all_users = false; + bool can_be_muted_only_for_self = false; + bool can_be_unmuted_only_for_self = false; + bool is_min = false; + bool is_fake = false; bool is_just_joined = false; bool is_speaking = false; int32 local_active_date = 0; int64 order = 0; + int32 pending_volume_level = 0; + uint64 pending_volume_level_generation = 0; + + bool have_pending_is_muted = false; + bool pending_is_muted_by_themselves = false; + bool pending_is_muted_by_admin = false; + bool pending_is_muted_locally = false; + uint64 pending_is_muted_generation = 0; + + static constexpr int32 MIN_VOLUME_LEVEL = 1; + static constexpr int32 MAX_VOLUME_LEVEL = 20000; + GroupCallParticipant() = default; explicit GroupCallParticipant(const tl_object_ptr &participant); static bool is_versioned_update(const tl_object_ptr &participant); + void update_from(const GroupCallParticipant &old_participant); + bool update_can_be_muted(bool can_manage, bool is_self, bool is_admin); + bool set_pending_is_muted(bool is_muted, bool can_manage, bool is_self, bool is_admin); + int64 get_real_order() const { return (static_cast(max(active_date, local_active_date)) << 32) + joined_date; } @@ -49,6 +72,16 @@ struct GroupCallParticipant { return user_id.is_valid(); } + bool get_is_muted_by_themselves() const; + + bool get_is_muted_by_admin() const; + + bool get_is_muted_locally() const; + + bool get_is_muted_for_all_users() const; + + int32 get_volume_level() const; + td_api::object_ptr get_group_call_participant_object( ContactsManager *contacts_manager) const; }; diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index 0ce6c70f3..4e2934b78 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -1583,8 +1583,8 @@ static Result create_input_message_content( string mime_type; if (file_id.is_valid()) { file_view = td->file_manager_->get_file_view(file_id); - auto suggested_name = file_view.suggested_name(); - const PathView path_view(suggested_name); + auto suggested_path = file_view.suggested_path(); + const PathView path_view(suggested_path); file_name = path_view.file_name().str(); mime_type = MimeType::from_extension(path_view.extension()); } @@ -1621,7 +1621,7 @@ static Result create_input_message_content( td->animations_manager_->create_animation( file_id, string(), thumbnail, AnimationSize(), has_stickers, std::move(sticker_file_ids), std::move(file_name), std::move(mime_type), input_animation->duration_, - get_dimensions(input_animation->width_, input_animation->height_), false); + get_dimensions(input_animation->width_, input_animation->height_, "inputMessageAnimation"), false); content = make_unique(file_id, std::move(caption)); break; @@ -1690,7 +1690,7 @@ static Result create_input_message_content( PhotoSize s; s.type = type; - s.dimensions = get_dimensions(input_photo->width_, input_photo->height_); + s.dimensions = get_dimensions(input_photo->width_, input_photo->height_, "inputMessagePhoto"); s.size = static_cast(file_view.size()); s.file_id = file_id; @@ -1713,9 +1713,10 @@ static Result create_input_message_content( emoji = std::move(input_sticker->emoji_); - td->stickers_manager_->create_sticker(file_id, string(), thumbnail, - get_dimensions(input_sticker->width_, input_sticker->height_), nullptr, - false, nullptr); + td->stickers_manager_->create_sticker( + file_id, string(), thumbnail, + get_dimensions(input_sticker->width_, input_sticker->height_, "inputMessageSticker"), nullptr, false, + nullptr); content = make_unique(file_id); break; @@ -1726,10 +1727,11 @@ static Result create_input_message_content( ttl = input_video->ttl_; bool has_stickers = !sticker_file_ids.empty(); - td->videos_manager_->create_video( - file_id, string(), thumbnail, AnimationSize(), has_stickers, std::move(sticker_file_ids), - std::move(file_name), std::move(mime_type), input_video->duration_, - get_dimensions(input_video->width_, input_video->height_), input_video->supports_streaming_, false); + td->videos_manager_->create_video(file_id, string(), thumbnail, AnimationSize(), has_stickers, + std::move(sticker_file_ids), std::move(file_name), std::move(mime_type), + input_video->duration_, + get_dimensions(input_video->width_, input_video->height_, "inputMessageVideo"), + input_video->supports_streaming_, false); content = make_unique(file_id, std::move(caption)); break; @@ -1743,7 +1745,7 @@ static Result create_input_message_content( } td->video_notes_manager_->create_video_note(file_id, string(), thumbnail, input_video_note->duration_, - get_dimensions(length, length), false); + get_dimensions(length, length, "inputMessageVideoNote"), false); content = make_unique(file_id, false); break; @@ -1834,7 +1836,8 @@ static Result create_input_message_content( PhotoSize s; s.type = 'n'; - s.dimensions = get_dimensions(input_invoice->photo_width_, input_invoice->photo_height_); + s.dimensions = + get_dimensions(input_invoice->photo_width_, input_invoice->photo_height_, "inputMessageInvoice"); s.size = input_invoice->photo_size_; // TODO use invoice_file_id size s.file_id = invoice_file_id; @@ -2075,7 +2078,7 @@ Result get_input_message_content( LOG(WARNING) << "Ignore thumbnail file: " << r_thumbnail_file_id.error().message(); } else { thumbnail.type = 't'; - thumbnail.dimensions = get_dimensions(input_thumbnail->width_, input_thumbnail->height_); + thumbnail.dimensions = get_dimensions(input_thumbnail->width_, input_thumbnail->height_, "inputThumbnail"); thumbnail.file_id = r_thumbnail_file_id.ok(); CHECK(thumbnail.file_id.is_valid()); @@ -2493,6 +2496,42 @@ tl_object_ptr get_input_media(const MessageContent *co return input_media; } +tl_object_ptr get_fake_input_media(Td *td, tl_object_ptr input_file, + FileId file_id) { + FileView file_view = td->file_manager_->get_file_view(file_id); + auto file_type = file_view.get_type(); + switch (file_type) { + case FileType::Animation: + case FileType::Audio: + case FileType::Document: + case FileType::Sticker: + case FileType::Video: + case FileType::VoiceNote: { + vector> attributes; + auto file_path = file_view.suggested_path(); + const PathView path_view(file_path); + Slice file_name = path_view.file_name(); + if (!file_name.empty()) { + attributes.push_back(make_tl_object(file_name.str())); + } + string mime_type = MimeType::from_extension(path_view.extension()); + int32 flags = 0; + if (file_type == FileType::Video) { + flags |= telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK; + } + return make_tl_object( + flags, false /*ignored*/, false /*ignored*/, std::move(input_file), nullptr, mime_type, std::move(attributes), + vector>(), 0); + } + case FileType::Photo: + return make_tl_object( + 0, std::move(input_file), vector>(), 0); + default: + UNREACHABLE(); + } + return nullptr; +} + void delete_message_content_thumbnail(MessageContent *content, Td *td) { switch (content->get_type()) { case MessageContentType::Animation: { @@ -4146,7 +4185,7 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const if (to_secret && !file_view.is_encrypted_secret()) { auto download_file_id = file_manager->dup_file_id(file_id); file_id = file_manager - ->register_generate(FileType::Encrypted, FileLocationSource::FromServer, file_view.suggested_name(), + ->register_generate(FileType::Encrypted, FileLocationSource::FromServer, file_view.suggested_path(), PSTRING() << "#file_id#" << download_file_id.get(), dialog_id, file_view.size()) .ok(); } @@ -4486,6 +4525,10 @@ unique_ptr get_action_message_content(Td *td, tl_object_ptrflags_ & telegram_api::messageActionPhoneCall::DURATION_MASK) != 0 ? phone_call->duration_ : 0; auto is_video = (phone_call->flags_ & telegram_api::messageActionPhoneCall::VIDEO_MASK) != 0; + if (duration < 0) { + LOG(ERROR) << "Receive invalid " << oneline(to_string(phone_call)); + break; + } return make_unique(phone_call->call_id_, duration, get_call_discard_reason(phone_call->reason_), is_video); } @@ -4583,6 +4626,14 @@ unique_ptr get_action_message_content(Td *td, tl_object_ptr(InputGroupCallId(invite_to_group_call->call_), std::move(user_ids)); } + case telegram_api::messageActionSetMessagesTTL::ID: { + auto set_messages_ttl = move_tl_object_as(action); + if (set_messages_ttl->period_ < 0) { + LOG(ERROR) << "Receive wrong TTL = " << set_messages_ttl->period_; + break; + } + return td::make_unique(set_messages_ttl->period_); + } default: UNREACHABLE(); } diff --git a/td/telegram/MessageContent.h b/td/telegram/MessageContent.h index c04038a6c..398d2d4c4 100644 --- a/td/telegram/MessageContent.h +++ b/td/telegram/MessageContent.h @@ -117,6 +117,9 @@ tl_object_ptr get_input_media(const MessageContent *co tl_object_ptr get_input_media(const MessageContent *content, Td *td, int32 ttl, const string &emoji, bool force); +tl_object_ptr get_fake_input_media(Td *td, tl_object_ptr input_file, + FileId file_id); + void delete_message_content_thumbnail(MessageContent *content, Td *td); bool can_forward_message_content(const MessageContent *content); diff --git a/td/telegram/MessageEntity.cpp b/td/telegram/MessageEntity.cpp index ec8a7dda8..0c2469459 100644 --- a/td/telegram/MessageEntity.cpp +++ b/td/telegram/MessageEntity.cpp @@ -677,9 +677,7 @@ static vector match_urls(Slice str) { url_begin_ptr = url_begin_ptr - 7; } else if (ends_with(protocol, "https")) { url_begin_ptr = url_begin_ptr - 8; - } else if (ends_with(protocol, "sftp")) { - url_begin_ptr = url_begin_ptr - 7; - } else if (ends_with(protocol, "ftp") && protocol != "tftp") { + } else if (ends_with(protocol, "ftp") && protocol != "tftp" && protocol != "sftp") { url_begin_ptr = url_begin_ptr - 6; } else { is_bad = true; @@ -983,8 +981,7 @@ Slice fix_url(Slice str) { bool has_protocol = false; auto str_begin = to_lower(str.substr(0, 8)); - if (begins_with(str_begin, "http://") || begins_with(str_begin, "https://") || begins_with(str_begin, "sftp://") || - begins_with(str_begin, "ftp://")) { + if (begins_with(str_begin, "http://") || begins_with(str_begin, "https://") || begins_with(str_begin, "ftp://")) { auto pos = str.find(':'); str = str.substr(pos + 3); has_protocol = true; @@ -3032,6 +3029,7 @@ Result> get_message_entities(const ContactsManager *contac vector> &&input_entities, bool allow_all) { vector entities; + entities.reserve(input_entities.size()); for (auto &entity : input_entities) { if (entity == nullptr || entity->type_ == nullptr) { continue; @@ -3756,23 +3754,32 @@ static void merge_new_entities(vector &entities, vector &entities, bool allow_empty, bool skip_new_entities, bool skip_bot_commands, bool for_draft) { - if (!check_utf8(text)) { - return Status::Error(400, "Strings must be encoded in UTF-8"); - } - - for (auto &entity : entities) { - if (entity.offset < 0 || entity.offset > 1000000) { - return Status::Error(400, PSLICE() << "Receive an entity with incorrect offset " << entity.offset); + string result; + if (entities.empty()) { + // fast path + if (!clean_input_string(text)) { + return Status::Error(400, "Strings must be encoded in UTF-8"); } - if (entity.length < 0 || entity.length > 1000000) { - return Status::Error(400, PSLICE() << "Receive an entity with incorrect length " << entity.length); + result = std::move(text); + } else { + if (!check_utf8(text)) { + return Status::Error(400, "Strings must be encoded in UTF-8"); } + + for (auto &entity : entities) { + if (entity.offset < 0 || entity.offset > 1000000) { + return Status::Error(400, PSLICE() << "Receive an entity with incorrect offset " << entity.offset); + } + if (entity.length < 0 || entity.length > 1000000) { + return Status::Error(400, PSLICE() << "Receive an entity with incorrect length " << entity.length); + } + } + td::remove_if(entities, [](const MessageEntity &entity) { return entity.length == 0; }); + + fix_entities(entities); + + TRY_RESULT_ASSIGN(result, clean_input_string_with_entities(text, entities)); } - td::remove_if(entities, [](const MessageEntity &entity) { return entity.length == 0; }); - - fix_entities(entities); - - TRY_RESULT(result, clean_input_string_with_entities(text, entities)); // now entities are still sorted by offset and length, but not type, // because some characters could be deleted and after that some entities begin to share a common end diff --git a/td/telegram/MessageTtlSetting.cpp b/td/telegram/MessageTtlSetting.cpp new file mode 100644 index 000000000..00adec19b --- /dev/null +++ b/td/telegram/MessageTtlSetting.cpp @@ -0,0 +1,27 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/MessageTtlSetting.h" + +namespace td { + +bool MessageTtlSetting::is_empty() const { + return ttl_period_ == 0; +} + +int32 MessageTtlSetting::get_message_ttl_setting_object() const { + return ttl_period_; +} + +bool operator==(const MessageTtlSetting &lhs, const MessageTtlSetting &rhs) { + return lhs.ttl_period_ == rhs.ttl_period_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const MessageTtlSetting &message_ttl_setting) { + return string_builder << "MessageTtlSetting[" << message_ttl_setting.ttl_period_ << "]"; +} + +} // namespace td diff --git a/td/telegram/MessageTtlSetting.h b/td/telegram/MessageTtlSetting.h new file mode 100644 index 000000000..7a7c9c690 --- /dev/null +++ b/td/telegram/MessageTtlSetting.h @@ -0,0 +1,56 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/tl_helpers.h" + +#include + +namespace td { + +class MessageTtlSetting { + int32 ttl_period_ = 0; + + friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageTtlSetting &message_ttl_setting); + + friend bool operator==(const MessageTtlSetting &lhs, const MessageTtlSetting &rhs); + + public: + MessageTtlSetting() = default; + + template ::value>> + MessageTtlSetting(T ttl_period) = delete; + + explicit MessageTtlSetting(int32 ttl_period) : ttl_period_(ttl_period) { + } + + bool is_empty() const; + + int32 get_message_ttl_setting_object() const; + + template + void store(StorerT &storer) const { + td::store(ttl_period_, storer); + } + + template + void parse(ParserT &parser) { + td::parse(ttl_period_, parser); + } +}; + +bool operator==(const MessageTtlSetting &lhs, const MessageTtlSetting &rhs); + +inline bool operator!=(const MessageTtlSetting &lhs, const MessageTtlSetting &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const MessageTtlSetting &message_ttl_setting); + +} // namespace td diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 294ffb751..2bc52ca18 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -13,6 +13,7 @@ #include "td/telegram/DialogDb.h" #include "td/telegram/DialogFilter.h" #include "td/telegram/DialogFilter.hpp" +#include "td/telegram/DialogInviteLink.h" #include "td/telegram/DialogLocation.h" #include "td/telegram/DraftMessage.h" #include "td/telegram/DraftMessage.hpp" @@ -65,16 +66,15 @@ #include "td/utils/algorithm.h" #include "td/utils/format.h" #include "td/utils/misc.h" +#include "td/utils/PathView.h" #include "td/utils/Random.h" #include "td/utils/Slice.h" #include "td/utils/Time.h" #include "td/utils/tl_helpers.h" -#include "td/utils/tl_storers.h" #include "td/utils/utf8.h" #include #include -#include #include #include #include @@ -142,11 +142,11 @@ class UpdateDialogFilterQuery : public Td::ResultHandler { } }; -class ReorderDialogFiltersQuery : public Td::ResultHandler { +class UpdateDialogFiltersOrderQuery : public Td::ResultHandler { Promise promise_; public: - explicit ReorderDialogFiltersQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit UpdateDialogFiltersOrderQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(vector dialog_filter_ids) { @@ -982,7 +982,7 @@ class CreateChannelQuery : public Td::ResultHandler { } void send(const string &title, bool is_megagroup, const string &about, const DialogLocation &location, - int64 random_id) { + bool for_import, int64 random_id) { int32 flags = 0; if (is_megagroup) { flags |= telegram_api::channels_createChannel::MEGAGROUP_MASK; @@ -992,11 +992,14 @@ class CreateChannelQuery : public Td::ResultHandler { if (!location.empty()) { flags |= telegram_api::channels_createChannel::GEO_POINT_MASK; } + if (for_import) { + flags |= telegram_api::channels_createChannel::FOR_IMPORT_MASK; + } random_id_ = random_id; send_query(G()->net_query_creator().create( - telegram_api::channels_createChannel(flags, false /*ignored*/, false /*ignored*/, title, about, - location.get_input_geo_point(), location.get_address()))); + telegram_api::channels_createChannel(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, title, + about, location.get_input_geo_point(), location.get_address()))); } void on_result(uint64 id, BufferSlice packet) override { @@ -1016,6 +1019,215 @@ class CreateChannelQuery : public Td::ResultHandler { } }; +class CheckHistoryImportQuery : public Td::ResultHandler { + Promise> promise_; + + public: + explicit CheckHistoryImportQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(const string &message_file_head) { + send_query(G()->net_query_creator().create(telegram_api::messages_checkHistoryImport(message_file_head))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for CheckHistoryImportQuery: " << to_string(ptr); + auto file_type = [&]() -> td_api::object_ptr { + if (ptr->pm_) { + return td_api::make_object(ptr->title_); + } else if (ptr->group_) { + return td_api::make_object(ptr->title_); + } else { + return td_api::make_object(); + } + }(); + promise_.set_value(std::move(file_type)); + } + + void on_error(uint64 id, Status status) override { + promise_.set_error(std::move(status)); + } +}; + +class CheckHistoryImportPeerQuery : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit CheckHistoryImportPeerQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id) { + dialog_id_ = dialog_id; + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + CHECK(input_peer != nullptr); + send_query(G()->net_query_creator().create(telegram_api::messages_checkHistoryImportPeer(std::move(input_peer)))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for CheckHistoryImportPeerQuery: " << to_string(ptr); + promise_.set_value(std::move(ptr->confirm_text_)); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "CheckHistoryImportPeerQuery"); + promise_.set_error(std::move(status)); + } +}; + +class InitHistoryImportQuery : public Td::ResultHandler { + Promise promise_; + FileId file_id_; + DialogId dialog_id_; + vector attached_file_ids_; + + public: + explicit InitHistoryImportQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, FileId file_id, tl_object_ptr &&input_file, + vector attached_file_ids) { + CHECK(input_file != nullptr); + file_id_ = file_id; + dialog_id_ = dialog_id; + attached_file_ids_ = std::move(attached_file_ids); + + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + CHECK(input_peer != nullptr); + send_query(G()->net_query_creator().create(telegram_api::messages_initHistoryImport( + std::move(input_peer), std::move(input_file), narrow_cast(attached_file_ids_.size())))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + td->file_manager_->delete_partial_remote_location(file_id_); + + auto ptr = result_ptr.move_as_ok(); + td->messages_manager_->start_import_messages(dialog_id_, ptr->id_, std::move(attached_file_ids_), + std::move(promise_)); + } + + void on_error(uint64 id, Status status) override { + if (FileReferenceManager::is_file_reference_error(status)) { + LOG(ERROR) << "Receive file reference error " << status; + } + if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) { + // TODO support FILE_PART_*_MISSING + } + + td->file_manager_->delete_partial_remote_location(file_id_); + + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "InitHistoryImportQuery"); + promise_.set_error(std::move(status)); + } +}; + +class UploadImportedMediaQuery : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + int64 import_id_; + FileId file_id_; + + public: + explicit UploadImportedMediaQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, int64 import_id, const string &file_name, FileId file_id, + tl_object_ptr &&input_media) { + CHECK(input_media != nullptr); + dialog_id_ = dialog_id; + import_id_ = import_id; + file_id_ = file_id; + + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + if (input_peer == nullptr) { + return on_error(0, Status::Error(400, "Have no write access to the chat")); + } + + send_query(G()->net_query_creator().create(telegram_api::messages_uploadImportedMedia( + std::move(input_peer), import_id, file_name, std::move(input_media)))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + td->file_manager_->delete_partial_remote_location(file_id_); + + // ignore response + + promise_.set_value(Unit()); + } + + void on_error(uint64 id, Status status) override { + if (FileReferenceManager::is_file_reference_error(status)) { + LOG(ERROR) << "Receive file reference error " << status; + } + if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) { + // TODO support FILE_PART_*_MISSING + } + + td->file_manager_->delete_partial_remote_location(file_id_); + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "UploadImportedMediaQuery"); + promise_.set_error(std::move(status)); + } +}; + +class StartImportHistoryQuery : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit StartImportHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, int64 import_id) { + dialog_id_ = dialog_id; + + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + CHECK(input_peer != nullptr); + + send_query( + G()->net_query_creator().create(telegram_api::messages_startHistoryImport(std::move(input_peer), import_id))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + if (!result_ptr.ok()) { + return on_error(id, Status::Error(500, "Import history returned false")); + } + promise_.set_value(Unit()); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "StartImportHistoryQuery"); + promise_.set_error(std::move(status)); + } +}; + class EditDialogPhotoQuery : public Td::ResultHandler { Promise promise_; FileId file_id_; @@ -1160,12 +1372,53 @@ class EditDialogTitleQuery : public Td::ResultHandler { } }; -class EditDialogDefaultBannedRightsQuery : public Td::ResultHandler { +class SetHistoryTtlQuery : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: - explicit EditDialogDefaultBannedRightsQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit SetHistoryTtlQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, int32 period) { + dialog_id_ = dialog_id; + + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + CHECK(input_peer != nullptr); + + send_query(G()->net_query_creator().create(telegram_api::messages_setHistoryTTL(std::move(input_peer), period))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SetHistoryTtlQuery: " << to_string(ptr); + td->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(uint64 id, Status status) override { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetHistoryTtlQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class EditChatDefaultBannedRightsQuery : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit EditChatDefaultBannedRightsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, RestrictedRights permissions) { @@ -1183,7 +1436,7 @@ class EditDialogDefaultBannedRightsQuery : public Td::ResultHandler { } auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for EditDialogPermissionsQuery: " << to_string(ptr); + LOG(INFO) << "Receive result for EditChatDefaultBannedRightsQuery: " << to_string(ptr); td->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } @@ -1194,7 +1447,7 @@ class EditDialogDefaultBannedRightsQuery : public Td::ResultHandler { return; } } else { - td->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditDialogDefaultBannedRightsQuery"); + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditChatDefaultBannedRightsQuery"); } promise_.set_error(std::move(status)); } @@ -2314,6 +2567,61 @@ class DeleteChannelHistoryQuery : public Td::ResultHandler { } }; +class DeletePhoneCallHistoryQuery : public Td::ResultHandler { + Promise promise_; + bool revoke_; + + void send_request() { + int32 flags = 0; + if (revoke_) { + flags |= telegram_api::messages_deletePhoneCallHistory::REVOKE_MASK; + } + send_query( + G()->net_query_creator().create(telegram_api::messages_deletePhoneCallHistory(flags, false /*ignored*/))); + } + + public: + explicit DeletePhoneCallHistoryQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(bool revoke) { + revoke_ = revoke; + + send_request(); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + auto affected_messages = result_ptr.move_as_ok(); + CHECK(affected_messages->get_id() == telegram_api::messages_affectedFoundMessages::ID); + + if (affected_messages->pts_count_ > 0) { + auto promise = affected_messages->offset_ > 0 ? Promise() : std::move(promise_); + auto pts = affected_messages->pts_; + auto pts_count = affected_messages->pts_count_; + auto update = + make_tl_object(std::move(affected_messages->messages_), pts, pts_count); + td->updates_manager_->add_pending_pts_update(std::move(update), pts, pts_count, std::move(promise), + "delete phone call history query"); + } else if (affected_messages->offset_ <= 0) { + promise_.set_value(Unit()); + } + + if (affected_messages->offset_ > 0) { + send_request(); + return; + } + } + + void on_error(uint64 id, Status status) override { + promise_.set_error(std::move(status)); + } +}; + class BlockFromRepliesQuery : public Td::ResultHandler { Promise promise_; @@ -2413,7 +2721,7 @@ class DeleteUserHistoryQuery : public Td::ResultHandler { } }; -class ReadAllMentionsQuery : public Td::ResultHandler { +class ReadMentionsQuery : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; @@ -2429,7 +2737,7 @@ class ReadAllMentionsQuery : public Td::ResultHandler { } public: - explicit ReadAllMentionsQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit ReadMentionsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id) { @@ -2449,7 +2757,7 @@ class ReadAllMentionsQuery : public Td::ResultHandler { if (affected_history->pts_count_ > 0) { if (dialog_id_.get_type() == DialogType::Channel) { - LOG(ERROR) << "Receive pts_count " << affected_history->pts_count_ << " in result of ReadAllMentionsQuery in " + LOG(ERROR) << "Receive pts_count " << affected_history->pts_count_ << " in result of ReadMentionsQuery in " << dialog_id_; td->updates_manager_->get_difference("Wrong messages_readMentions result"); } else { @@ -2468,7 +2776,7 @@ class ReadAllMentionsQuery : public Td::ResultHandler { } void on_error(uint64 id, Status status) override { - td->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadAllMentionsQuery"); + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadMentionsQuery"); promise_.set_error(std::move(status)); } }; @@ -2570,7 +2878,7 @@ class SendMessageActor : public NetActorOnce { } auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for SendMessageQuery for " << random_id_ << ": " << to_string(ptr); + LOG(INFO) << "Receive result for SendMessage for " << random_id_ << ": " << to_string(ptr); auto constructor_id = ptr->get_id(); if (constructor_id != telegram_api::updateShortSentMessage::ID) { @@ -2582,20 +2890,23 @@ class SendMessageActor : public NetActorOnce { std::move(sent_message->entities_)); auto message_id = MessageId(ServerMessageId(sent_message->id_)); + auto ttl_period = (sent_message->flags_ & telegram_api::updateShortSentMessage::TTL_PERIOD_MASK) != 0 + ? sent_message->ttl_period_ + : 0; + auto update = make_tl_object(random_id_, message_id, sent_message->date_, ttl_period); if (dialog_id_.get_type() == DialogType::Channel) { - td->messages_manager_->add_pending_channel_update( - dialog_id_, make_tl_object(random_id_, message_id, sent_message->date_), - sent_message->pts_, sent_message->pts_count_, Promise(), "send message actor"); + td->messages_manager_->add_pending_channel_update(dialog_id_, std::move(update), sent_message->pts_, + sent_message->pts_count_, Promise(), + "send message actor"); return; } - td->updates_manager_->add_pending_pts_update( - make_tl_object(random_id_, message_id, sent_message->date_), sent_message->pts_, - sent_message->pts_count_, Promise(), "send message actor"); + td->updates_manager_->add_pending_pts_update(std::move(update), sent_message->pts_, sent_message->pts_count_, + Promise(), "send message actor"); } void on_error(uint64 id, Status status) override { - LOG(INFO) << "Receive error for SendMessageQuery: " << status; + LOG(INFO) << "Receive error for SendMessage: " << status; if (G()->close_flag() && G()->parameters().use_message_db) { // do not send error, message will be re-sent return; @@ -2741,8 +3052,7 @@ class SendMultiMediaActor : public NetActorOnce { } auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for SendMultiMediaQuery for " << format::as_array(random_ids_) << ": " - << to_string(ptr); + LOG(INFO) << "Receive result for SendMultiMedia for " << format::as_array(random_ids_) << ": " << to_string(ptr); auto sent_random_ids = UpdatesManager::get_sent_messages_random_ids(ptr.get()); bool is_result_wrong = false; @@ -2773,7 +3083,7 @@ class SendMultiMediaActor : public NetActorOnce { } } if (is_result_wrong) { - LOG(ERROR) << "Receive wrong result for SendMultiMediaQuery with random_ids " << format::as_array(random_ids_) + LOG(ERROR) << "Receive wrong result for SendMultiMedia with random_ids " << format::as_array(random_ids_) << " to " << dialog_id_ << ": " << oneline(to_string(ptr)); td->updates_manager_->schedule_get_difference("Wrong sendMultiMedia result"); } @@ -2782,7 +3092,7 @@ class SendMultiMediaActor : public NetActorOnce { } void on_error(uint64 id, Status status) override { - LOG(INFO) << "Receive error for SendMultiMediaQuery: " << status; + LOG(INFO) << "Receive error for SendMultiMedia: " << status; if (G()->close_flag() && G()->parameters().use_message_db) { // do not send error, message will be re-sent return; @@ -2870,13 +3180,13 @@ class SendMediaActor : public NetActorOnce { } auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for SendMediaQuery for " << random_id_ << ": " << to_string(ptr); + LOG(INFO) << "Receive result for SendMedia for " << random_id_ << ": " << to_string(ptr); td->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendMedia"); td->updates_manager_->on_get_updates(std::move(ptr), Promise()); } void on_error(uint64 id, Status status) override { - LOG(INFO) << "Receive error for SendMediaQuery: " << status; + LOG(INFO) << "Receive error for SendMedia: " << status; if (G()->close_flag() && G()->parameters().use_message_db) { // do not send error, message will be re-sent return; @@ -3121,7 +3431,7 @@ class EditMessageActor : public NetActorOnce { } void on_error(uint64 id, Status status) override { - LOG(INFO) << "Receive error for EditMessageQuery: " << status; + LOG(INFO) << "Receive error for EditMessage: " << status; if (!td->auth_manager_->is_bot() && status.message() == "MESSAGE_NOT_MODIFIED") { return promise_.set_value(0); } @@ -3232,12 +3542,12 @@ class SetGameScoreActor : public NetActorOnce { } auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for SetGameScoreActor: " << to_string(ptr); + LOG(INFO) << "Receive result for SetGameScore: " << to_string(ptr); td->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(uint64 id, Status status) override { - LOG(INFO) << "Receive error for SetGameScoreQuery: " << status; + LOG(INFO) << "Receive error for SetGameScore: " << status; td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetGameScoreActor"); promise_.set_error(std::move(status)); } @@ -3422,8 +3732,7 @@ class ForwardMessagesActor : public NetActorOnce { } auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for ForwardMessagesQuery for " << format::as_array(random_ids_) << ": " - << to_string(ptr); + LOG(INFO) << "Receive result for ForwardMessages for " << format::as_array(random_ids_) << ": " << to_string(ptr); auto sent_random_ids = UpdatesManager::get_sent_messages_random_ids(ptr.get()); bool is_result_wrong = false; auto sent_random_ids_size = sent_random_ids.size(); @@ -4062,7 +4371,7 @@ class UpdatePeerSettingsQuery : public Td::ResultHandler { dialog_id_, make_tl_object(0, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, 0), + false /*ignored*/, false /*ignored*/, 0), true); promise_.set_value(Unit()); @@ -4104,7 +4413,7 @@ class ReportEncryptedSpamQuery : public Td::ResultHandler { dialog_id_, make_tl_object(0, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, 0), + false /*ignored*/, false /*ignored*/, 0), true); promise_.set_value(Unit()); @@ -4128,19 +4437,19 @@ class ReportPeerQuery : public Td::ResultHandler { explicit ReportPeerQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(DialogId dialog_id, tl_object_ptr &&report_reason, - const vector &message_ids) { + void send(DialogId dialog_id, const vector &message_ids, ReportReason &&report_reason) { dialog_id_ = dialog_id; auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); if (message_ids.empty()) { - send_query(G()->net_query_creator().create( - telegram_api::account_reportPeer(std::move(input_peer), std::move(report_reason)))); + send_query(G()->net_query_creator().create(telegram_api::account_reportPeer( + std::move(input_peer), report_reason.get_input_report_reason(), report_reason.get_message()))); } else { - send_query(G()->net_query_creator().create(telegram_api::messages_report( - std::move(input_peer), MessagesManager::get_server_message_ids(message_ids), std::move(report_reason)))); + send_query(G()->net_query_creator().create( + telegram_api::messages_report(std::move(input_peer), MessagesManager::get_server_message_ids(message_ids), + report_reason.get_input_report_reason(), report_reason.get_message()))); } } @@ -4169,6 +4478,70 @@ class ReportPeerQuery : public Td::ResultHandler { } }; +class ReportProfilePhotoQuery : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + FileId file_id_; + string file_reference_; + ReportReason report_reason_; + + public: + explicit ReportProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, FileId file_id, tl_object_ptr &&input_photo, + ReportReason &&report_reason) { + dialog_id_ = dialog_id; + file_id_ = file_id; + file_reference_ = FileManager::extract_file_reference(input_photo); + report_reason_ = std::move(report_reason); + + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); + CHECK(input_peer != nullptr); + + send_query(G()->net_query_creator().create(telegram_api::account_reportProfilePhoto( + std::move(input_peer), std::move(input_photo), report_reason_.get_input_report_reason(), + report_reason_.get_message()))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + if (!result) { + return on_error(id, Status::Error(400, "Receive false as result")); + } + + promise_.set_value(Unit()); + } + + void on_error(uint64 id, Status status) override { + LOG(INFO) << "Receive error for report chat photo: " << status; + if (!td->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { + VLOG(file_references) << "Receive " << status << " for " << file_id_; + td->file_manager_->delete_file_reference(file_id_, file_reference_); + td->file_reference_manager_->repair_file_reference( + file_id_, + PromiseCreator::lambda([dialog_id = dialog_id_, file_id = file_id_, report_reason = std::move(report_reason_), + promise = std::move(promise_)](Result result) mutable { + if (result.is_error()) { + LOG(INFO) << "Reported photo " << file_id << " is likely to be deleted"; + return promise.set_value(Unit()); + } + send_closure(G()->messages_manager(), &MessagesManager::report_dialog_photo, dialog_id, file_id, + std::move(report_reason), std::move(promise)); + })); + return; + } + + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportProfilePhotoQuery"); + promise_.set_error(std::move(status)); + } +}; + class EditPeerFoldersQuery : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; @@ -4543,6 +4916,42 @@ class MessagesManager::UploadDialogPhotoCallback : public FileManager::UploadCal } }; +class MessagesManager::UploadImportedMessagesCallback : public FileManager::UploadCallback { + public: + void on_upload_ok(FileId file_id, tl_object_ptr input_file) override { + send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_messages, file_id, + std::move(input_file)); + } + void on_upload_encrypted_ok(FileId file_id, tl_object_ptr input_file) override { + UNREACHABLE(); + } + void on_upload_secure_ok(FileId file_id, tl_object_ptr input_file) override { + UNREACHABLE(); + } + void on_upload_error(FileId file_id, Status error) override { + send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_messages_error, file_id, + std::move(error)); + } +}; + +class MessagesManager::UploadImportedMessageAttachmentCallback : public FileManager::UploadCallback { + public: + void on_upload_ok(FileId file_id, tl_object_ptr input_file) override { + send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_message_attachment, file_id, + std::move(input_file)); + } + void on_upload_encrypted_ok(FileId file_id, tl_object_ptr input_file) override { + UNREACHABLE(); + } + void on_upload_secure_ok(FileId file_id, tl_object_ptr input_file) override { + UNREACHABLE(); + } + void on_upload_error(FileId file_id, Status error) override { + send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_message_attachment_error, file_id, + std::move(error)); + } +}; + template void MessagesManager::Message::store(StorerT &storer) const { using td::store; @@ -4580,6 +4989,8 @@ void MessagesManager::Message::store(StorerT &storer) const { bool has_linked_top_thread_message_id = linked_top_thread_message_id.is_valid(); bool has_interaction_info_update_date = interaction_info_update_date != 0; bool has_send_emoji = !send_emoji.empty(); + bool is_imported = is_forwarded && forward_info->is_imported; + bool has_ttl_period = ttl_period != 0; BEGIN_STORE_FLAGS(); STORE_FLAG(is_channel_post); STORE_FLAG(is_outgoing); @@ -4639,6 +5050,8 @@ void MessagesManager::Message::store(StorerT &storer) const { STORE_FLAG(is_pinned); STORE_FLAG(has_interaction_info_update_date); STORE_FLAG(has_send_emoji); + STORE_FLAG(is_imported); + STORE_FLAG(has_ttl_period); // 25 END_STORE_FLAGS(); } @@ -4751,6 +5164,9 @@ void MessagesManager::Message::store(StorerT &storer) const { if (has_reply_markup) { store(reply_markup, storer); } + if (has_ttl_period) { + store(ttl_period, storer); + } } // do not forget to resolve message dependencies @@ -4790,6 +5206,8 @@ void MessagesManager::Message::parse(ParserT &parser) { bool has_linked_top_thread_message_id = false; bool has_interaction_info_update_date = false; bool has_send_emoji = false; + bool is_imported = false; + bool has_ttl_period = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_channel_post); PARSE_FLAG(is_outgoing); @@ -4849,6 +5267,8 @@ void MessagesManager::Message::parse(ParserT &parser) { PARSE_FLAG(is_pinned); PARSE_FLAG(has_interaction_info_update_date); PARSE_FLAG(has_send_emoji); + PARSE_FLAG(is_imported); + PARSE_FLAG(has_ttl_period); END_PARSE_FLAGS(); } @@ -4890,6 +5310,7 @@ void MessagesManager::Message::parse(ParserT &parser) { if (has_forward_psa_type) { parse(forward_info->psa_type, parser); } + forward_info->is_imported = is_imported; } if (has_real_forward_from) { parse(real_forward_from_dialog_id, parser); @@ -4967,6 +5388,9 @@ void MessagesManager::Message::parse(ParserT &parser) { if (has_reply_markup) { parse(reply_markup, parser); } + if (has_ttl_period) { + parse(ttl_period, parser); + } is_content_secret |= is_secret_message_content(ttl, content->get_type()); // repair is_content_secret for old messages @@ -5032,6 +5456,7 @@ void MessagesManager::Dialog::store(StorerT &storer) const { bool has_distance = distance >= 0; bool has_last_yet_unsent_message = last_message_id.is_valid() && last_message_id.is_yet_unsent(); bool has_active_group_call_id = active_group_call_id.is_valid(); + bool has_message_ttl_setting = !message_ttl_setting.is_empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_draft_message); STORE_FLAG(has_last_database_message); @@ -5090,6 +5515,9 @@ void MessagesManager::Dialog::store(StorerT &storer) const { STORE_FLAG(has_active_group_call); STORE_FLAG(is_group_call_empty); STORE_FLAG(has_active_group_call_id); + STORE_FLAG(can_invite_members); + STORE_FLAG(has_message_ttl_setting); + STORE_FLAG(is_message_ttl_setting_inited); END_STORE_FLAGS(); } @@ -5177,6 +5605,9 @@ void MessagesManager::Dialog::store(StorerT &storer) const { if (has_active_group_call_id) { store(active_group_call_id, storer); } + if (has_message_ttl_setting) { + store(message_ttl_setting, storer); + } } // do not forget to resolve dialog dependencies including dependencies of last_message @@ -5207,6 +5638,7 @@ void MessagesManager::Dialog::parse(ParserT &parser) { bool has_pending_read_channel_inbox = false; bool has_distance = false; bool has_active_group_call_id = false; + bool has_message_ttl_setting = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_draft_message); PARSE_FLAG(has_last_database_message); @@ -5265,6 +5697,9 @@ void MessagesManager::Dialog::parse(ParserT &parser) { PARSE_FLAG(has_active_group_call); PARSE_FLAG(is_group_call_empty); PARSE_FLAG(has_active_group_call_id); + PARSE_FLAG(can_invite_members); + PARSE_FLAG(has_message_ttl_setting); + PARSE_FLAG(is_message_ttl_setting_inited); END_PARSE_FLAGS(); } else { is_folder_id_inited = false; @@ -5283,6 +5718,8 @@ void MessagesManager::Dialog::parse(ParserT &parser) { is_is_blocked_inited = false; has_active_group_call = false; is_group_call_empty = false; + can_invite_members = false; + is_message_ttl_setting_inited = false; } parse(last_new_message_id, parser); @@ -5402,6 +5839,9 @@ void MessagesManager::Dialog::parse(ParserT &parser) { if (has_active_group_call_id) { parse(active_group_call_id, parser); } + if (has_message_ttl_setting) { + parse(message_ttl_setting, parser); + } } template @@ -5472,6 +5912,8 @@ MessagesManager::MessagesManager(Td *td, ActorShared<> parent) : td_(td), parent upload_media_callback_ = std::make_shared(); upload_thumbnail_callback_ = std::make_shared(); upload_dialog_photo_callback_ = std::make_shared(); + upload_imported_messages_callback_ = std::make_shared(); + upload_imported_message_attachment_callback_ = std::make_shared(); channel_get_difference_timeout_.set_callback(on_channel_get_difference_timeout_callback); channel_get_difference_timeout_.set_callback_data(static_cast(this)); @@ -6242,7 +6684,8 @@ void MessagesManager::skip_old_pending_pts_update(tl_object_ptrrandom_id_, update_sent_message->message_id_, - update_sent_message->date_, FileId(), "process old updateSentMessage"); + update_sent_message->date_, update_sent_message->ttl_period_, FileId(), + "process old updateSentMessage"); return; } else { LOG(ERROR) << "Receive awaited sent " << update_sent_message->message_id_ << " from " << source << " with pts " @@ -6920,6 +7363,13 @@ void MessagesManager::on_user_dialog_action(DialogId dialog_id, MessageId top_th } return; } + { + auto message_import_progress = action.get_importing_messages_action_progress(); + if (message_import_progress >= 0) { + // TODO + return; + } + } if (!td_->contacts_manager_->have_min_user(user_id)) { LOG(DEBUG) << "Ignore typing of unknown " << user_id; @@ -7024,10 +7474,8 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p VLOG(messages) << "Receive from " << source << " pending " << to_string(update); CHECK(update != nullptr); if (dialog_id.get_type() != DialogType::Channel) { - if (dialog_id != DialogId() || !td_->auth_manager_->is_bot()) { - LOG(WARNING) << "Receive channel update in invalid " << dialog_id << " from " << source << ": " - << oneline(to_string(update)); - } + LOG(WARNING) << "Receive channel update in invalid " << dialog_id << " from " << source << ": " + << oneline(to_string(update)); promise.set_value(Unit()); return; } @@ -7107,7 +7555,8 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p if (being_sent_messages_.count(update_sent_message->random_id_) > 0) { // apply sent channel message on_send_message_success(update_sent_message->random_id_, update_sent_message->message_id_, - update_sent_message->date_, FileId(), "process old updateSentChannelMessage"); + update_sent_message->date_, update_sent_message->ttl_period_, FileId(), + "process old updateSentChannelMessage"); promise.set_value(Unit()); return; } @@ -7127,7 +7576,7 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p return; } - if (old_pts + pts_count != new_pts) { + if (old_pts != new_pts - pts_count) { LOG(INFO) << "Found a gap in the " << dialog_id << " with pts = " << old_pts << ". new_pts = " << new_pts << ", pts_count = " << pts_count << " in update from " << source; @@ -7180,10 +7629,11 @@ void MessagesManager::process_pts_update(tl_object_ptr &&u break; } case updateSentMessage::ID: { - auto send_message_success_update = move_tl_object_as(update); - LOG(INFO) << "Process updateSentMessage " << send_message_success_update->random_id_; - on_send_message_success(send_message_success_update->random_id_, send_message_success_update->message_id_, - send_message_success_update->date_, FileId(), "process updateSentMessage"); + auto update_sent_message = move_tl_object_as(update); + LOG(INFO) << "Process updateSentMessage " << update_sent_message->random_id_; + on_send_message_success(update_sent_message->random_id_, update_sent_message->message_id_, + update_sent_message->date_, update_sent_message->ttl_period_, FileId(), + "process updateSentMessage"); break; } case telegram_api::updateReadMessagesContents::ID: { @@ -7254,10 +7704,11 @@ void MessagesManager::process_channel_update(tl_object_ptr LOG(INFO) << "Process dummyUpdate"; break; case updateSentMessage::ID: { - auto send_message_success_update = move_tl_object_as(update); - LOG(INFO) << "Process updateSentMessage " << send_message_success_update->random_id_; - on_send_message_success(send_message_success_update->random_id_, send_message_success_update->message_id_, - send_message_success_update->date_, FileId(), "process updateSentChannelMessage"); + auto update_sent_message = move_tl_object_as(update); + LOG(INFO) << "Process updateSentMessage " << update_sent_message->random_id_; + on_send_message_success(update_sent_message->random_id_, update_sent_message->message_id_, + update_sent_message->date_, update_sent_message->ttl_period_, FileId(), + "process updateSentChannelMessage"); break; } case telegram_api::updateNewChannelMessage::ID: { @@ -7282,7 +7733,7 @@ void MessagesManager::process_channel_update(tl_object_ptr } auto dialog_id = DialogId(channel_id); - delete_dialog_messages_from_updates(dialog_id, message_ids, false); + delete_dialog_messages(dialog_id, message_ids, true, false); break; } case telegram_api::updateEditChannelMessage::ID: { @@ -7825,7 +8276,7 @@ void MessagesManager::hide_dialog_action_bar(Dialog *d) { return; } if (!d->can_report_spam && !d->can_add_contact && !d->can_block_user && !d->can_share_phone_number && - !d->can_report_location && !d->can_unarchive && d->distance < 0) { + !d->can_report_location && !d->can_unarchive && d->distance < 0 && !d->can_invite_members) { return; } @@ -7836,6 +8287,7 @@ void MessagesManager::hide_dialog_action_bar(Dialog *d) { d->can_report_location = false; d->can_unarchive = false; d->distance = -1; + d->can_invite_members = false; send_update_chat_action_bar(d); } @@ -7865,7 +8317,7 @@ void MessagesManager::remove_dialog_action_bar(DialogId dialog_id, Promise } if (!d->can_report_spam && !d->can_add_contact && !d->can_block_user && !d->can_share_phone_number && - !d->can_report_location && !d->can_unarchive && d->distance < 0) { + !d->can_report_location && !d->can_unarchive && d->distance < 0 && !d->can_invite_members) { return promise.set_value(Unit()); } @@ -7975,8 +8427,8 @@ bool MessagesManager::can_report_dialog(DialogId dialog_id) const { } } -void MessagesManager::report_dialog(DialogId dialog_id, const tl_object_ptr &reason, - const vector &message_ids, Promise &&promise) { +void MessagesManager::report_dialog(DialogId dialog_id, const vector &message_ids, ReportReason &&reason, + Promise &&promise) { Dialog *d = get_dialog_force(dialog_id); if (d == nullptr) { return promise.set_error(Status::Error(3, "Chat not found")); @@ -7986,14 +8438,10 @@ void MessagesManager::report_dialog(DialogId dialog_id, const tl_object_ptrcan_report_spam; - if (reason->get_id() == td_api::chatReportReasonSpam::ID && message_ids.empty()) { + if (reason.is_spam() && message_ids.empty()) { // report from action bar if (dialog_id.get_type() == DialogType::SecretChat) { auto user_dialog_id = DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); @@ -8032,46 +8480,39 @@ void MessagesManager::report_dialog(DialogId dialog_id, const tl_object_ptr report_reason; - switch (reason->get_id()) { - case td_api::chatReportReasonSpam::ID: - report_reason = make_tl_object(); - break; - case td_api::chatReportReasonViolence::ID: - report_reason = make_tl_object(); - break; - case td_api::chatReportReasonPornography::ID: - report_reason = make_tl_object(); - break; - case td_api::chatReportReasonChildAbuse::ID: - report_reason = make_tl_object(); - break; - case td_api::chatReportReasonCopyright::ID: - report_reason = make_tl_object(); - break; - case td_api::chatReportReasonUnrelatedLocation::ID: - report_reason = make_tl_object(); - if (dialog_id.get_type() == DialogType::Channel) { - hide_dialog_action_bar(d); - } - break; - case td_api::chatReportReasonCustom::ID: { - auto other_reason = static_cast(reason.get()); - auto text = other_reason->text_; - if (!clean_input_string(text)) { - return promise.set_error(Status::Error(400, "Text must be encoded in UTF-8")); - } - - report_reason = make_tl_object(text); - break; - } - default: - UNREACHABLE(); + if (dialog_id.get_type() == DialogType::Channel && reason.is_unrelated_location()) { + hide_dialog_action_bar(d); } - CHECK(report_reason != nullptr); - td_->create_handler(std::move(promise)) - ->send(dialog_id, std::move(report_reason), server_message_ids); + td_->create_handler(std::move(promise))->send(dialog_id, server_message_ids, std::move(reason)); +} + +void MessagesManager::report_dialog_photo(DialogId dialog_id, FileId file_id, ReportReason &&reason, + Promise &&promise) { + Dialog *d = get_dialog_force(dialog_id); + if (d == nullptr) { + return promise.set_error(Status::Error(3, "Chat not found")); + } + + if (!have_input_peer(dialog_id, AccessRights::Read)) { + return promise.set_error(Status::Error(3, "Can't access the chat")); + } + + if (!can_report_dialog(dialog_id)) { + return promise.set_error(Status::Error(3, "Chat photo can't be reported")); + } + + auto file_view = td_->file_manager_->get_file_view(file_id); + if (file_view.empty()) { + return promise.set_error(Status::Error(400, "Unknown file ID")); + } + if (file_view.get_type() != FileType::Photo || !file_view.has_remote_location() || + !file_view.remote_location().is_photo()) { + return promise.set_error(Status::Error(400, "Only full chat photos can be reported")); + } + + td_->create_handler(std::move(promise)) + ->send(dialog_id, file_id, file_view.remote_location().as_input_photo(), std::move(reason)); } void MessagesManager::on_get_peer_settings(DialogId dialog_id, @@ -8098,9 +8539,11 @@ void MessagesManager::on_get_peer_settings(DialogId dialog_id, auto can_unarchive = (peer_settings->flags_ & telegram_api::peerSettings::AUTOARCHIVED_MASK) != 0; auto distance = (peer_settings->flags_ & telegram_api::peerSettings::GEO_DISTANCE_MASK) != 0 ? peer_settings->geo_distance_ : -1; + auto can_invite_members = (peer_settings->flags_ & telegram_api::peerSettings::INVITE_MEMBERS_MASK) != 0; if (d->can_report_spam == can_report_spam && d->can_add_contact == can_add_contact && d->can_block_user == can_block_user && d->can_share_phone_number == can_share_phone_number && - d->can_report_location == can_report_location && d->can_unarchive == can_unarchive && d->distance == distance) { + d->can_report_location == can_report_location && d->can_unarchive == can_unarchive && d->distance == distance && + d->can_invite_members == can_invite_members) { if (!d->know_action_bar || !d->know_can_report_spam) { d->know_can_report_spam = true; d->know_action_bar = true; @@ -8118,6 +8561,7 @@ void MessagesManager::on_get_peer_settings(DialogId dialog_id, d->can_report_location = can_report_location; d->can_unarchive = can_unarchive; d->distance = distance < 0 ? -1 : distance; + d->can_invite_members = can_invite_members; fix_dialog_action_bar(d); @@ -8142,10 +8586,28 @@ void MessagesManager::fix_dialog_action_bar(Dialog *d) { if (dialog_type != DialogType::Channel) { LOG(ERROR) << "Receive can_report_location in " << d->dialog_id; d->can_report_location = false; + } else if (d->can_report_spam || d->can_add_contact || d->can_block_user || d->can_share_phone_number || + d->can_unarchive || d->can_invite_members) { + LOG(ERROR) << "Receive action bar " << d->can_report_spam << "/" << d->can_add_contact << "/" << d->can_block_user + << "/" << d->can_share_phone_number << "/" << d->can_report_location << "/" << d->can_unarchive << "/" + << d->can_invite_members; + d->can_report_spam = false; + d->can_add_contact = false; + d->can_block_user = false; + d->can_share_phone_number = false; + d->can_unarchive = false; + d->can_invite_members = false; + CHECK(d->distance == -1); + } + } + if (d->can_invite_members) { + if (dialog_type != DialogType::Chat && (dialog_type != DialogType::Channel || is_broadcast_channel(d->dialog_id))) { + LOG(ERROR) << "Receive can_invite_members in " << d->dialog_id; + d->can_invite_members = false; } else if (d->can_report_spam || d->can_add_contact || d->can_block_user || d->can_share_phone_number || d->can_unarchive) { LOG(ERROR) << "Receive action bar " << d->can_report_spam << "/" << d->can_add_contact << "/" << d->can_block_user - << "/" << d->can_share_phone_number << "/" << d->can_report_location << "/" << d->can_unarchive; + << "/" << d->can_share_phone_number << "/" << d->can_unarchive << "/" << d->can_invite_members; d->can_report_spam = false; d->can_add_contact = false; d->can_block_user = false; @@ -8177,6 +8639,7 @@ void MessagesManager::fix_dialog_action_bar(Dialog *d) { } if (d->can_share_phone_number) { CHECK(!d->can_report_location); + CHECK(!d->can_invite_members); if (dialog_type != DialogType::User) { LOG(ERROR) << "Receive can_share_phone_number in " << d->dialog_id; d->can_share_phone_number = false; @@ -8191,6 +8654,7 @@ void MessagesManager::fix_dialog_action_bar(Dialog *d) { } if (d->can_block_user) { CHECK(!d->can_report_location); + CHECK(!d->can_invite_members); CHECK(!d->can_share_phone_number); if (dialog_type != DialogType::User) { LOG(ERROR) << "Receive can_block_user in " << d->dialog_id; @@ -8204,6 +8668,7 @@ void MessagesManager::fix_dialog_action_bar(Dialog *d) { } if (d->can_add_contact) { CHECK(!d->can_report_location); + CHECK(!d->can_invite_members); CHECK(!d->can_share_phone_number); if (dialog_type != DialogType::User) { LOG(ERROR) << "Receive can_add_contact in " << d->dialog_id; @@ -8417,7 +8882,7 @@ void MessagesManager::do_send_media(DialogId dialog_id, Message *m, FileId file_ bool have_input_thumbnail = input_thumbnail != nullptr; LOG(INFO) << "Do send media file " << file_id << " with thumbnail " << thumbnail_file_id << ", have_input_file = " << have_input_file << ", have_input_thumbnail = " << have_input_thumbnail - << ", ttl = " << m->ttl; + << ", TTL = " << m->ttl; MessageContent *content = nullptr; if (m->message_id.is_any_server()) { @@ -8665,6 +9130,138 @@ void MessagesManager::on_upload_dialog_photo_error(FileId file_id, Status status promise.set_error(std::move(status)); } +void MessagesManager::on_upload_imported_messages(FileId file_id, tl_object_ptr input_file) { + LOG(INFO) << "File " << file_id << " has been uploaded"; + + auto it = being_uploaded_imported_messages_.find(file_id); + if (it == being_uploaded_imported_messages_.end()) { + // just in case, as in on_upload_media + return; + } + + CHECK(it->second != nullptr); + DialogId dialog_id = it->second->dialog_id; + vector attached_file_ids = std::move(it->second->attached_file_ids); + bool is_reupload = it->second->is_reupload; + Promise promise = std::move(it->second->promise); + + being_uploaded_imported_messages_.erase(it); + + TRY_STATUS_PROMISE(promise, can_send_message(dialog_id)); + + FileView file_view = td_->file_manager_->get_file_view(file_id); + CHECK(!file_view.is_encrypted()); + if (input_file == nullptr && file_view.has_remote_location()) { + if (file_view.main_remote_location().is_web()) { + return promise.set_error(Status::Error(400, "Can't use web file")); + } + if (is_reupload) { + return promise.set_error(Status::Error(400, "Failed to reupload the file")); + } + + CHECK(file_view.get_type() == FileType::Document); + // delete file reference and forcely reupload the file + auto file_reference = FileManager::extract_file_reference(file_view.main_remote_location().as_input_document()); + td_->file_manager_->delete_file_reference(file_id, file_reference); + upload_imported_messages(dialog_id, file_id, std::move(attached_file_ids), true, std::move(promise), {-1}); + return; + } + CHECK(input_file != nullptr); + + td_->create_handler(std::move(promise)) + ->send(dialog_id, file_id, std::move(input_file), std::move(attached_file_ids)); +} + +void MessagesManager::on_upload_imported_messages_error(FileId file_id, Status status) { + if (G()->close_flag()) { + // do not fail upload if closing + return; + } + + LOG(INFO) << "File " << file_id << " has upload error " << status; + CHECK(status.is_error()); + + auto it = being_uploaded_imported_messages_.find(file_id); + if (it == being_uploaded_imported_messages_.end()) { + // just in case, as in on_upload_media_error + return; + } + + Promise promise = std::move(it->second->promise); + + being_uploaded_imported_messages_.erase(it); + + promise.set_error(std::move(status)); +} + +void MessagesManager::on_upload_imported_message_attachment(FileId file_id, + tl_object_ptr input_file) { + LOG(INFO) << "File " << file_id << " has been uploaded"; + + auto it = being_uploaded_imported_message_attachments_.find(file_id); + if (it == being_uploaded_imported_message_attachments_.end()) { + // just in case, as in on_upload_media + return; + } + + CHECK(it->second != nullptr); + DialogId dialog_id = it->second->dialog_id; + int64 import_id = it->second->import_id; + bool is_reupload = it->second->is_reupload; + Promise promise = std::move(it->second->promise); + + being_uploaded_imported_message_attachments_.erase(it); + + FileView file_view = td_->file_manager_->get_file_view(file_id); + CHECK(!file_view.is_encrypted()); + if (input_file == nullptr && file_view.has_remote_location()) { + if (file_view.main_remote_location().is_web()) { + return promise.set_error(Status::Error(400, "Can't use web file")); + } + if (is_reupload) { + return promise.set_error(Status::Error(400, "Failed to reupload the file")); + } + + // delete file reference and forcely reupload the file + auto file_reference = + file_view.get_type() == FileType::Photo + ? FileManager::extract_file_reference(file_view.main_remote_location().as_input_photo()) + : FileManager::extract_file_reference(file_view.main_remote_location().as_input_document()); + td_->file_manager_->delete_file_reference(file_id, file_reference); + upload_imported_message_attachment(dialog_id, import_id, file_id, true, std::move(promise), {-1}); + return; + } + CHECK(input_file != nullptr); + + auto suggested_path = file_view.suggested_path(); + const PathView path_view(suggested_path); + td_->create_handler(std::move(promise)) + ->send(dialog_id, import_id, path_view.file_name().str(), file_id, + get_fake_input_media(td_, std::move(input_file), file_id)); +} + +void MessagesManager::on_upload_imported_message_attachment_error(FileId file_id, Status status) { + if (G()->close_flag()) { + // do not fail upload if closing + return; + } + + LOG(INFO) << "File " << file_id << " has upload error " << status; + CHECK(status.is_error()); + + auto it = being_uploaded_imported_message_attachments_.find(file_id); + if (it == being_uploaded_imported_message_attachments_.end()) { + // just in case, as in on_upload_media_error + return; + } + + Promise promise = std::move(it->second->promise); + + being_uploaded_imported_message_attachments_.erase(it); + + promise.set_error(std::move(status)); +} + void MessagesManager::before_get_difference() { running_get_difference_ = true; @@ -8713,6 +9310,7 @@ void MessagesManager::after_get_difference() { auto dialog_id = full_message_id.get_dialog_id(); auto message_id = full_message_id.get_message_id(); CHECK(message_id.is_valid()); + CHECK(message_id.is_server()); switch (dialog_id.get_type()) { case DialogType::Channel: // get channel difference may prevent updates from being applied @@ -8725,11 +9323,14 @@ void MessagesManager::after_get_difference() { if (!have_message_force({dialog_id, it.second}, "after get difference")) { // The sent message has already been deleted by the user or sent to inaccessible channel. // The sent message may never be received, but we will need updateMessageId in case the message is received - // to delete it from the server and to not add to the chat. - // But if the chat is inaccessible, then likely we will be unable to delete the message from server and - // will delete it from the chat just after it is added. So we remove updateMessageId for such messages in + // to delete it from the server and not add to the chat. + // But if the chat is inaccessible or the message is in an inaccessible chat part, then we will not be able to + // add the message or delete it from the server. In this case we forget updateMessageId for such messages in // order to not check them over and over. - if (!have_input_peer(dialog_id, AccessRights::Read)) { + const Dialog *d = get_dialog(dialog_id); + if (!have_input_peer(dialog_id, AccessRights::Read) || + (d != nullptr && + message_id <= td::max(d->last_clear_history_message_id, d->max_unavailable_message_id))) { update_message_ids_to_delete.push_back(it.first); } break; @@ -8740,16 +9341,14 @@ void MessagesManager::after_get_difference() { LOG(ERROR) << "Unknown dialog " << dialog_id; break; } - if (dialog_id.get_type() == DialogType::Channel || message_id.is_scheduled() || - message_id <= d->last_new_message_id) { + if (dialog_id.get_type() == DialogType::Channel || message_id <= d->last_new_message_id) { LOG(ERROR) << "Receive updateMessageId from " << it.second << " to " << full_message_id << " but not receive corresponding message, last_new_message_id = " << d->last_new_message_id; } - if (dialog_id.get_type() != DialogType::Channel && - (message_id.is_scheduled() || message_id <= d->last_new_message_id)) { + if (dialog_id.get_type() != DialogType::Channel && message_id <= d->last_new_message_id) { dump_debug_message_op(get_dialog(dialog_id)); } - if (message_id.is_scheduled() || message_id <= d->last_new_message_id) { + if (message_id <= d->last_new_message_id) { get_message_from_server(it.first, PromiseCreator::lambda([this, full_message_id](Result result) { if (result.is_error()) { LOG(WARNING) @@ -8800,7 +9399,7 @@ void MessagesManager::after_get_difference() { void MessagesManager::on_get_empty_messages(DialogId dialog_id, vector empty_message_ids) { if (!empty_message_ids.empty()) { - delete_dialog_messages_from_updates(dialog_id, std::move(empty_message_ids), true); + delete_dialog_messages(dialog_id, std::move(empty_message_ids), true, true); } } @@ -8888,8 +9487,9 @@ void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_ CHECK(offset < 0 || from_the_end); CHECK(!from_message_id.is_scheduled()); - // it is likely that there are no more history messages on the server - bool have_full_history = from_the_end && narrow_cast(messages.size()) < limit; + // the server can return less messages than requested if some of messages are deleted during request + // but if it happens, it is likely that there are no more messages on the server + bool have_full_history = from_the_end && narrow_cast(messages.size()) < limit && messages.size() <= 1; Dialog *d = get_dialog(dialog_id); if (messages.empty()) { @@ -8939,7 +9539,7 @@ void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_ MessageId last_added_message_id; bool have_next = false; - if (narrow_cast(messages.size()) < limit + offset && d != nullptr) { + if (narrow_cast(messages.size()) < limit + offset && messages.size() <= 1 && d != nullptr) { MessageId first_received_message_id = get_message_id(messages.back(), false); if (first_received_message_id >= from_message_id && d->first_database_message_id.is_valid() && first_received_message_id >= d->first_database_message_id) { @@ -9060,9 +9660,10 @@ void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_ // LOG_IF(ERROR, d->first_message_id.is_valid() && d->first_message_id > first_received_message_id) // << "Receive " << first_received_message_id << ", but first chat message is " << d->first_message_id; + bool intersect_last_database_message_ids = + last_added_message_id >= d->first_database_message_id && d->last_database_message_id >= first_added_message_id; bool need_update_database_message_ids = - last_added_message_id.is_valid() && (from_the_end || (last_added_message_id >= d->first_database_message_id && - d->last_database_message_id >= first_added_message_id)); + last_added_message_id.is_valid() && (from_the_end || intersect_last_database_message_ids); if (from_the_end) { if (!d->last_new_message_id.is_valid()) { set_dialog_last_new_message_id( @@ -9076,22 +9677,42 @@ void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_ } if (need_update_database_message_ids) { - bool is_dialog_updated = false; + if (from_the_end && !intersect_last_database_message_ids && d->last_database_message_id.is_valid()) { + if (d->last_database_message_id < first_added_message_id || last_added_message_id == d->last_message_id) { + set_dialog_first_database_message_id(d, MessageId(), "on_get_history 1"); + set_dialog_last_database_message_id(d, MessageId(), "on_get_history 1"); + } else { + auto min_message_id = td::min(d->first_database_message_id, d->last_message_id); + CHECK(last_added_message_id < min_message_id); + if (min_message_id <= last_added_message_id.get_next_message_id(MessageType::Server)) { + // connect local messages with last received server message + set_dialog_first_database_message_id(d, last_added_message_id, "on_get_history 2"); + } else { + LOG(WARNING) << "Have last " << d->last_message_id << " and first database " << d->first_database_message_id + << " in " << dialog_id << ", but received history from the end only up to " + << last_added_message_id; + // can't connect messages, because there can be unknown server messages after last_added_message_id + } + } + } if (!d->last_database_message_id.is_valid()) { CHECK(d->last_message_id.is_valid()); MessagesConstIterator it(d, d->last_message_id); + MessageId new_first_database_message_id; while (*it != nullptr) { auto message_id = (*it)->message_id; if (message_id.is_server() || message_id.is_local()) { if (!d->last_database_message_id.is_valid()) { set_dialog_last_database_message_id(d, message_id, "on_get_history"); } - set_dialog_first_database_message_id(d, message_id, "on_get_history"); + new_first_database_message_id = message_id; try_restore_dialog_reply_markup(d, *it); } --it; } - is_dialog_updated = true; + if (new_first_database_message_id.is_valid()) { + set_dialog_first_database_message_id(d, new_first_database_message_id, "on_get_history"); + } } else { LOG_CHECK(d->last_new_message_id.is_valid()) << dialog_id << " " << from_the_end << " " << d->first_database_message_id << " " @@ -9106,28 +9727,34 @@ void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_ { MessagesConstIterator it(d, d->first_database_message_id); if (*it != nullptr && ((*it)->message_id == d->first_database_message_id || (*it)->have_next)) { + MessageId new_first_database_message_id = d->first_database_message_id; while (*it != nullptr) { auto message_id = (*it)->message_id; - if ((message_id.is_server() || message_id.is_local()) && message_id < d->first_database_message_id) { - set_dialog_first_database_message_id(d, message_id, "on_get_history 2"); + if ((message_id.is_server() || message_id.is_local()) && message_id < new_first_database_message_id) { + new_first_database_message_id = message_id; try_restore_dialog_reply_markup(d, *it); - is_dialog_updated = true; } --it; } + if (new_first_database_message_id != d->first_database_message_id) { + set_dialog_first_database_message_id(d, new_first_database_message_id, "on_get_history 2"); + } } } { MessagesConstIterator it(d, d->last_database_message_id); if (*it != nullptr && ((*it)->message_id == d->last_database_message_id || (*it)->have_next)) { + MessageId new_last_database_message_id = d->last_database_message_id; while (*it != nullptr) { auto message_id = (*it)->message_id; - if ((message_id.is_server() || message_id.is_local()) && message_id > d->last_database_message_id) { - set_dialog_last_database_message_id(d, message_id, "on_get_history 2"); - is_dialog_updated = true; + if ((message_id.is_server() || message_id.is_local()) && message_id > new_last_database_message_id) { + new_last_database_message_id = message_id; } ++it; } + if (new_last_database_message_id != d->last_database_message_id) { + set_dialog_last_database_message_id(d, new_last_database_message_id, "on_get_history 2"); + } } } } @@ -9147,10 +9774,6 @@ void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_ first_message_id = first_added_message_id; } } - - if (is_dialog_updated) { - on_dialog_updated(dialog_id, "on_get_history"); - } } } @@ -9454,10 +10077,8 @@ void MessagesManager::on_get_scheduled_server_messages(DialogId dialog_id, uint3 for (auto &message : messages) { auto message_dialog_id = get_message_dialog_id(message); if (message_dialog_id != dialog_id) { - if (dialog_id.is_valid()) { - LOG(ERROR) << "Receive " << get_message_id(message, true) << " in wrong " << message_dialog_id << " instead of " - << dialog_id << ": " << oneline(to_string(message)); - } + LOG(ERROR) << "Receive " << get_message_id(message, true) << " in wrong " << message_dialog_id << " instead of " + << dialog_id << ": " << oneline(to_string(message)); continue; } @@ -9612,12 +10233,12 @@ void MessagesManager::delete_messages_from_updates(const vector &mess } } -void MessagesManager::delete_dialog_messages_from_updates(DialogId dialog_id, const vector &message_ids, - bool skip_update_for_not_found_messages) { - CHECK(dialog_id.get_type() == DialogType::Channel || dialog_id.get_type() == DialogType::SecretChat); +void MessagesManager::delete_dialog_messages(DialogId dialog_id, const vector &message_ids, + bool from_updates, bool skip_update_for_not_found_messages) { Dialog *d = get_dialog_force(dialog_id); if (d == nullptr) { LOG(INFO) << "Ignore deleteChannelMessages for unknown " << dialog_id; + CHECK(from_updates); CHECK(dialog_id.get_type() == DialogType::Channel); return; } @@ -9625,14 +10246,20 @@ void MessagesManager::delete_dialog_messages_from_updates(DialogId dialog_id, co vector deleted_message_ids; bool need_update_dialog_pos = false; for (auto message_id : message_ids) { - if (!message_id.is_valid() || (!message_id.is_server() && dialog_id.get_type() != DialogType::SecretChat)) { - LOG(ERROR) << "Incoming update tries to delete " << message_id; - continue; + CHECK(!message_id.is_scheduled()); + if (from_updates) { + if (!message_id.is_valid() || (!message_id.is_server() && dialog_id.get_type() != DialogType::SecretChat)) { + LOG(ERROR) << "Incoming update tries to delete " << message_id; + continue; + } + } else { + CHECK(message_id.is_valid()); } - auto message = delete_message(d, message_id, true, &need_update_dialog_pos, "updates"); + bool was_already_deleted = d->deleted_message_ids.count(message_id) != 0; + auto message = delete_message(d, message_id, true, &need_update_dialog_pos, "delete_dialog_messages"); if (message == nullptr) { - if (!skip_update_for_not_found_messages) { + if (!skip_update_for_not_found_messages && !was_already_deleted) { deleted_message_ids.push_back(message_id.get()); } } else { @@ -9640,7 +10267,7 @@ void MessagesManager::delete_dialog_messages_from_updates(DialogId dialog_id, co } } if (need_update_dialog_pos) { - send_update_chat_last_message(d, "delete_dialog_messages_from_updates"); + send_update_chat_last_message(d, "delete_dialog_messages"); } send_update_delete_messages(dialog_id, std::move(deleted_message_ids), true, false); } @@ -9798,7 +10425,7 @@ bool MessagesManager::can_delete_message(DialogId dialog_id, const Message *m) c if (m == nullptr) { return true; } - if (m->message_id.is_local()) { + if (m->message_id.is_local() || m->message_id.is_yet_unsent()) { return true; } switch (dialog_id.get_type()) { @@ -9875,6 +10502,9 @@ bool MessagesManager::can_revoke_message(DialogId dialog_id, const Message *m) c void MessagesManager::delete_messages(DialogId dialog_id, const vector &input_message_ids, bool revoke, Promise &&promise) { + if (G()->close_flag()) { + return promise.set_error(Status::Error(500, "Request aborted")); + } Dialog *d = get_dialog_force(dialog_id); if (d == nullptr) { return promise.set_error(Status::Error(6, "Chat is not found")); @@ -9934,8 +10564,10 @@ void MessagesManager::delete_messages(DialogId dialog_id, const vector deleted_message_ids; for (auto message_id : message_ids) { + need_update_chat_has_scheduled_messages |= message_id.is_scheduled(); auto m = delete_message(d, message_id, true, &need_update_dialog_pos, DELETE_MESSAGE_USER_REQUEST_SOURCE); if (m == nullptr) { LOG(INFO) << "Can't delete " << message_id << " because it is not found"; @@ -9949,7 +10581,9 @@ void MessagesManager::delete_messages(DialogId dialog_id, const vector &&promise) { + delete_all_call_messages_from_server(revoke, 0, std::move(promise)); +} + +class MessagesManager::DeleteAllCallMessagesFromServerLogEvent { + public: + bool revoke_; + + template + void store(StorerT &storer) const { + BEGIN_STORE_FLAGS(); + STORE_FLAG(revoke_); + END_STORE_FLAGS(); + } + + template + void parse(ParserT &parser) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(revoke_); + END_PARSE_FLAGS(); + } +}; + +uint64 MessagesManager::save_delete_all_call_messages_from_server_log_event(bool revoke) { + DeleteAllCallMessagesFromServerLogEvent log_event{revoke}; + return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteAllCallMessagesFromServer, + get_log_event_storer(log_event)); +} + +void MessagesManager::delete_all_call_messages_from_server(bool revoke, uint64 log_event_id, Promise &&promise) { + if (log_event_id == 0) { + log_event_id = save_delete_all_call_messages_from_server_log_event(revoke); + } + + auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise)); + promise = std::move(new_promise); // to prevent self-move + + td_->create_handler(std::move(promise))->send(revoke); +} + void MessagesManager::find_messages(const Message *m, vector &message_ids, const std::function &condition) { if (m == nullptr) { @@ -10426,16 +11100,19 @@ void MessagesManager::unload_dialog(DialogId dialog_id) { if (G()->close_flag()) { return; } - if (!is_message_unload_enabled()) { - // just in case - return; - } Dialog *d = get_dialog(dialog_id); if (d == nullptr) { LOG(ERROR) << "Unknown dialog " << dialog_id; return; } + CHECK(d->has_unload_timeout); + + if (!is_message_unload_enabled()) { + // just in case + d->has_unload_timeout = false; + return; + } vector to_unload_message_ids; int32 left_to_unload = 0; @@ -10461,6 +11138,8 @@ void MessagesManager::unload_dialog(DialogId dialog_id) { if (left_to_unload > 0) { LOG(DEBUG) << "Need to unload " << left_to_unload << " messages more in " << dialog_id; pending_unload_dialog_timeout_.add_timeout_in(d->dialog_id.get(), get_unload_dialog_delay()); + } else { + d->has_unload_timeout = false; } } @@ -10478,10 +11157,10 @@ void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dia MessageId max_message_id = d->last_database_message_id.is_valid() ? d->last_database_message_id : d->last_new_message_id; if (max_message_id.is_valid()) { - read_history_inbox(d->dialog_id, max_message_id, -1, "delete_all_dialog_messages"); + read_history_inbox(d->dialog_id, max_message_id, -1, "delete_all_dialog_messages 1"); } if (d->server_unread_count != 0 || d->local_unread_count != 0) { - set_dialog_last_read_inbox_message_id(d, MessageId::min(), 0, 0, true, "delete_all_dialog_messages"); + set_dialog_last_read_inbox_message_id(d, MessageId::min(), 0, 0, true, "delete_all_dialog_messages 2"); } } @@ -10507,7 +11186,7 @@ void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dia vector deleted_message_ids; do_delete_all_dialog_messages(d, d->messages, is_permanently_deleted, deleted_message_ids); - delete_all_dialog_messages_from_database(d, MessageId::max(), "delete_all_dialog_messages"); + delete_all_dialog_messages_from_database(d, MessageId::max(), "delete_all_dialog_messages 3"); if (is_permanently_deleted) { for (auto id : deleted_message_ids) { d->deleted_message_ids.insert(MessageId{id}); @@ -10518,9 +11197,10 @@ void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dia set_dialog_reply_markup(d, MessageId()); } - set_dialog_first_database_message_id(d, MessageId(), "delete_all_dialog_messages"); - set_dialog_last_database_message_id(d, MessageId(), "delete_all_dialog_messages"); - set_dialog_last_clear_history_date(d, last_message_date, last_clear_history_message_id, "delete_all_dialog_messages"); + set_dialog_first_database_message_id(d, MessageId(), "delete_all_dialog_messages 4"); + set_dialog_last_database_message_id(d, MessageId(), "delete_all_dialog_messages 5"); + set_dialog_last_clear_history_date(d, last_message_date, last_clear_history_message_id, + "delete_all_dialog_messages 6"); d->last_read_all_mentions_message_id = MessageId(); // it is not needed anymore d->message_notification_group.max_removed_notification_id = NotificationId(); // it is not needed anymore d->message_notification_group.max_removed_message_id = MessageId(); // it is not needed anymore @@ -10530,25 +11210,25 @@ void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dia d->notification_id_to_message_id.clear(); if (has_last_message_id) { - set_dialog_last_message_id(d, MessageId(), "delete_all_dialog_messages"); - send_update_chat_last_message(d, "delete_all_dialog_messages"); + set_dialog_last_message_id(d, MessageId(), "delete_all_dialog_messages 7"); + send_update_chat_last_message(d, "delete_all_dialog_messages 8"); } if (remove_from_dialog_list) { - set_dialog_order(d, DEFAULT_ORDER, true, false, "delete_all_dialog_messages 1"); + set_dialog_order(d, DEFAULT_ORDER, true, false, "delete_all_dialog_messages 9"); } else { - update_dialog_pos(d, "delete_all_dialog_messages 2"); + update_dialog_pos(d, "delete_all_dialog_messages 10"); } - on_dialog_updated(d->dialog_id, "delete_all_dialog_messages"); + on_dialog_updated(d->dialog_id, "delete_all_dialog_messages 11"); send_update_delete_messages(d->dialog_id, std::move(deleted_message_ids), is_permanently_deleted, false); } -void MessagesManager::delete_dialog(DialogId dialog_id) { +void MessagesManager::on_dialog_deleted(DialogId dialog_id, Promise &&promise) { LOG(INFO) << "Delete " << dialog_id; Dialog *d = get_dialog_force(dialog_id); if (d == nullptr) { - return; + return promise.set_value(Unit()); } delete_all_dialog_messages(d, true, false); @@ -10561,6 +11241,7 @@ void MessagesManager::delete_dialog(DialogId dialog_id) { } close_dialog(d); + promise.set_value(Unit()); } void MessagesManager::on_update_dialog_group_call_rights(DialogId dialog_id) { @@ -10667,7 +11348,7 @@ void MessagesManager::read_all_dialog_mentions_on_server(DialogId dialog_id, uin } LOG(INFO) << "Read all mentions on server in " << dialog_id; - td_->create_handler(get_erase_log_event_promise(log_event_id, std::move(promise))) + td_->create_handler(get_erase_log_event_promise(log_event_id, std::move(promise))) ->send(dialog_id); } @@ -10828,7 +11509,7 @@ void MessagesManager::repair_server_unread_count(DialogId dialog_id, int32 unrea create_actor("RepairServerUnreadCountSleepActor", 0.2, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Result result) { send_closure(actor_id, &MessagesManager::send_get_dialog_query, dialog_id, Promise(), - 0); + 0, "repair_server_unread_count"); })) .release(); } @@ -11444,9 +12125,9 @@ void MessagesManager::on_update_dialog_online_member_count_timeout(DialogId dial if (participant_count == 0 || participant_count >= 195) { td_->create_handler()->send(dialog_id); } else { - td_->contacts_manager_->send_get_channel_participants_query( - dialog_id.get_channel_id(), - ChannelParticipantsFilter(td_api::make_object()), 0, 200, 0, Auto()); + td_->contacts_manager_->get_channel_participants(dialog_id.get_channel_id(), + td_api::make_object(), + string(), 0, 200, 200, true, Auto()); } return; } @@ -11481,8 +12162,10 @@ MessageId MessagesManager::get_message_id(const tl_object_ptr &message_ptr) { switch (message_ptr->get_id()) { - case telegram_api::messageEmpty::ID: - return DialogId(); + case telegram_api::messageEmpty::ID: { + auto message = static_cast(message_ptr.get()); + return message->peer_id_ == nullptr ? DialogId() : DialogId(message->peer_id_); + } case telegram_api::message::ID: { auto message = static_cast(message_ptr.get()); return DialogId(message->peer_id_); @@ -11577,12 +12260,11 @@ bool MessagesManager::ttl_on_open(Dialog *d, Message *m, double now, bool is_loc } void MessagesManager::ttl_register_message(DialogId dialog_id, const Message *m, double now) { - if (m->ttl_expires_at == 0) { - return; - } + CHECK(m != nullptr); + CHECK(m->ttl_expires_at != 0); CHECK(!m->message_id.is_scheduled()); - auto it_flag = ttl_nodes_.insert(TtlNode(dialog_id, m->message_id)); + auto it_flag = ttl_nodes_.emplace(dialog_id, m->message_id, false); CHECK(it_flag.second); auto it = it_flag.first; @@ -11590,14 +12272,27 @@ void MessagesManager::ttl_register_message(DialogId dialog_id, const Message *m, ttl_update_timeout(now); } -void MessagesManager::ttl_unregister_message(DialogId dialog_id, const Message *m, double now, const char *source) { +void MessagesManager::ttl_period_register_message(DialogId dialog_id, const Message *m, double server_time) { + CHECK(m != nullptr); + CHECK(m->ttl_period != 0); + CHECK(!m->message_id.is_scheduled()); + + auto it_flag = ttl_nodes_.emplace(dialog_id, m->message_id, true); + CHECK(it_flag.second); + auto it = it_flag.first; + + auto now = Time::now(); + ttl_heap_.insert(now + (m->date + m->ttl_period - server_time), it->as_heap_node()); + ttl_update_timeout(now); +} + +void MessagesManager::ttl_unregister_message(DialogId dialog_id, const Message *m, const char *source) { if (m->ttl_expires_at == 0) { return; } CHECK(!m->message_id.is_scheduled()); - TtlNode ttl_node(dialog_id, m->message_id); - auto it = ttl_nodes_.find(ttl_node); + auto it = ttl_nodes_.find(TtlNode(dialog_id, m->message_id, false)); // expect m->ttl == 0, but m->ttl_expires_at > 0 from binlog LOG_CHECK(it != ttl_nodes_.end()) << dialog_id << " " << m->message_id << " " << source << " " << G()->close_flag() @@ -11609,15 +12304,34 @@ void MessagesManager::ttl_unregister_message(DialogId dialog_id, const Message * ttl_heap_.erase(heap_node); } ttl_nodes_.erase(it); - ttl_update_timeout(now); + ttl_update_timeout(Time::now()); +} + +void MessagesManager::ttl_period_unregister_message(DialogId dialog_id, const Message *m) { + if (m->ttl_period == 0) { + return; + } + CHECK(!m->message_id.is_scheduled()); + + auto it = ttl_nodes_.find(TtlNode(dialog_id, m->message_id, true)); + + CHECK(it != ttl_nodes_.end()); + + auto *heap_node = it->as_heap_node(); + if (heap_node->in_heap()) { + ttl_heap_.erase(heap_node); + } + ttl_nodes_.erase(it); + ttl_update_timeout(Time::now()); } void MessagesManager::ttl_loop(double now) { std::unordered_map, DialogIdHash> to_delete; while (!ttl_heap_.empty() && ttl_heap_.top_key() < now) { - auto full_message_id = TtlNode::from_heap_node(ttl_heap_.pop())->full_message_id; + TtlNode *ttl_node = TtlNode::from_heap_node(ttl_heap_.pop()); + auto full_message_id = ttl_node->full_message_id_; auto dialog_id = full_message_id.get_dialog_id(); - if (dialog_id.get_type() == DialogType::SecretChat) { + if (dialog_id.get_type() == DialogType::SecretChat || ttl_node->by_ttl_period_) { to_delete[dialog_id].push_back(full_message_id.get_message_id()); } else { auto d = get_dialog(dialog_id); @@ -11632,7 +12346,7 @@ void MessagesManager::ttl_loop(double now) { } } for (auto &it : to_delete) { - delete_dialog_messages_from_updates(it.first, it.second, false); + delete_dialog_messages(it.first, it.second, false, true); } ttl_update_timeout(now); } @@ -11653,7 +12367,7 @@ void MessagesManager::on_message_ttl_expired(Dialog *d, Message *m) { CHECK(m != nullptr); CHECK(m->ttl > 0); CHECK(d->dialog_id.get_type() != DialogType::SecretChat); - ttl_unregister_message(d->dialog_id, m, Time::now(), "on_message_ttl_expired"); + ttl_unregister_message(d->dialog_id, m, "on_message_ttl_expired"); unregister_message_content(td_, m->content.get(), {d->dialog_id, m->message_id}, "on_message_ttl_expired"); remove_message_file_sources(d->dialog_id, m); on_message_ttl_expired_impl(d, m); @@ -11797,7 +12511,7 @@ void MessagesManager::init() { if (!dialog_filters.empty()) { DialogFiltersLogEvent log_event; if (log_event_parse(log_event, dialog_filters).is_ok()) { - dialog_filters_updated_date_ = G()->ignore_backgrond_updates() ? 0 : log_event.updated_date; + dialog_filters_updated_date_ = G()->ignore_background_updates() ? 0 : log_event.updated_date; std::unordered_set server_dialog_filter_ids; for (auto &dialog_filter : log_event.server_dialog_filters_out) { if (server_dialog_filter_ids.insert(dialog_filter->dialog_filter_id).second) { @@ -12292,7 +13006,7 @@ void MessagesManager::on_send_secret_message_success(int64 random_id, MessageId } } - on_send_message_success(random_id, message_id, date, new_file_id, "process send_secret_message_success"); + on_send_message_success(random_id, message_id, date, 0, new_file_id, "process send_secret_message_success"); } void MessagesManager::delete_secret_messages(SecretChatId secret_chat_id, std::vector random_ids, @@ -12341,12 +13055,12 @@ void MessagesManager::finish_delete_secret_messages(DialogId dialog_id, std::vec LOG(INFO) << "Skip deletion of service " << message_id; } } - delete_dialog_messages_from_updates(dialog_id, to_delete_message_ids, false); + delete_dialog_messages(dialog_id, to_delete_message_ids, true, false); } -void MessagesManager::delete_secret_chat_history(SecretChatId secret_chat_id, MessageId last_message_id, - Promise<> promise) { - LOG(DEBUG) << "On delete history in " << secret_chat_id << " up to " << last_message_id; +void MessagesManager::delete_secret_chat_history(SecretChatId secret_chat_id, bool remove_from_dialog_list, + MessageId last_message_id, Promise<> promise) { + LOG(DEBUG) << "Delete history in " << secret_chat_id << " up to " << last_message_id; CHECK(secret_chat_id.is_valid()); CHECK(!last_message_id.is_scheduled()); @@ -12362,14 +13076,14 @@ void MessagesManager::delete_secret_chat_history(SecretChatId secret_chat_id, Me pending_secret_message->type = PendingSecretMessage::Type::DeleteHistory; pending_secret_message->dialog_id = dialog_id; pending_secret_message->last_message_id = last_message_id; + pending_secret_message->remove_from_dialog_list = remove_from_dialog_list; add_secret_message(std::move(pending_secret_message)); } -void MessagesManager::finish_delete_secret_chat_history(DialogId dialog_id, MessageId last_message_id, - Promise<> promise) { +void MessagesManager::finish_delete_secret_chat_history(DialogId dialog_id, bool remove_from_dialog_list, + MessageId last_message_id, Promise<> promise) { LOG(DEBUG) << "Delete history in " << dialog_id << " up to " << last_message_id; - promise.set_value(Unit()); // TODO: set after event is saved Dialog *d = get_dialog(dialog_id); if (d == nullptr) { LOG(ERROR) << "Unknown dialog " << dialog_id; @@ -12377,7 +13091,8 @@ void MessagesManager::finish_delete_secret_chat_history(DialogId dialog_id, Mess } // TODO: probably last_message_id is not needed - delete_all_dialog_messages(d, false, true); + delete_all_dialog_messages(d, remove_from_dialog_list, true); + promise.set_value(Unit()); // TODO: set after event is saved } void MessagesManager::read_secret_chat_outbox(SecretChatId secret_chat_id, int32 up_to_date, int32 read_date) { @@ -12521,17 +13236,14 @@ void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId LOG(WARNING) << "Receive invalid bot username " << message->via_bot_name_; message->via_bot_name_.clear(); } - if ((message->flags_ & secret_api::decryptedMessage::VIA_BOT_NAME_MASK) != 0 && !message->via_bot_name_.empty()) { - pending_secret_message->load_data_multipromise.add_promise( - PromiseCreator::lambda([this, via_bot_name = message->via_bot_name_, &flags = message_info.flags, - &via_bot_user_id = message_info.via_bot_user_id](Unit) mutable { - auto dialog_id = resolve_dialog_username(via_bot_name); - if (dialog_id.is_valid() && dialog_id.get_type() == DialogType::User) { - flags |= MESSAGE_FLAG_IS_SENT_VIA_BOT; - via_bot_user_id = dialog_id.get_user_id(); - } - })); - search_public_dialog(message->via_bot_name_, false, pending_secret_message->load_data_multipromise.get_promise()); + if (!message->via_bot_name_.empty()) { + auto request_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), via_bot_username = message->via_bot_name_, message_info_ptr = &message_info, + promise = pending_secret_message->load_data_multipromise.get_promise()](Unit) mutable { + send_closure(actor_id, &MessagesManager::on_resolve_secret_chat_message_via_bot_username, via_bot_username, + message_info_ptr, std::move(promise)); + }); + search_public_dialog(message->via_bot_name_, false, std::move(request_promise)); } if ((message->flags_ & secret_api::decryptedMessage::GROUPED_ID_MASK) != 0 && message->grouped_id_ != 0) { message_info.media_album_id = message->grouped_id_; @@ -12546,6 +13258,23 @@ void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId add_secret_message(std::move(pending_secret_message), std::move(lock_promise)); } +void MessagesManager::on_resolve_secret_chat_message_via_bot_username(const string &via_bot_username, + MessageInfo *message_info_ptr, + Promise &&promise) { + if (!G()->close_flag()) { + auto dialog_id = resolve_dialog_username(via_bot_username); + if (dialog_id.is_valid() && dialog_id.get_type() == DialogType::User) { + auto user_id = dialog_id.get_user_id(); + auto r_bot_data = td_->contacts_manager_->get_bot_data(user_id); + if (r_bot_data.is_ok() && r_bot_data.ok().is_inline) { + message_info_ptr->flags |= MESSAGE_FLAG_IS_SENT_VIA_BOT; + message_info_ptr->via_bot_user_id = user_id; + } + } + } + promise.set_value(Unit()); +} + void MessagesManager::on_secret_chat_screenshot_taken(SecretChatId secret_chat_id, UserId user_id, MessageId message_id, int32 date, int64 random_id, Promise<> promise) { LOG(DEBUG) << "On screenshot taken in " << secret_chat_id; @@ -12577,13 +13306,13 @@ void MessagesManager::on_secret_chat_screenshot_taken(SecretChatId secret_chat_i void MessagesManager::on_secret_chat_ttl_changed(SecretChatId secret_chat_id, UserId user_id, MessageId message_id, int32 date, int32 ttl, int64 random_id, Promise<> promise) { - LOG(DEBUG) << "On ttl set in " << secret_chat_id << " to " << ttl; + LOG(DEBUG) << "On TTL set in " << secret_chat_id << " to " << ttl; CHECK(secret_chat_id.is_valid()); CHECK(user_id.is_valid()); CHECK(message_id.is_valid()); CHECK(date > 0); if (ttl < 0) { - LOG(WARNING) << "Receive wrong ttl = " << ttl; + LOG(WARNING) << "Receive wrong TTL = " << ttl; promise.set_value(Unit()); return; } @@ -12641,8 +13370,9 @@ void MessagesManager::finish_add_secret_message(unique_ptr std::move(pending_secret_message->success_promise)); } if (pending_secret_message->type == PendingSecretMessage::Type::DeleteHistory) { - return finish_delete_secret_chat_history(pending_secret_message->dialog_id, pending_secret_message->last_message_id, - std::move(pending_secret_message->success_promise)); + return finish_delete_secret_chat_history( + pending_secret_message->dialog_id, pending_secret_message->remove_from_dialog_list, + pending_secret_message->last_message_id, std::move(pending_secret_message->success_promise)); } auto d = get_dialog(pending_secret_message->message_info.dialog_id); @@ -12707,6 +13437,9 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( if (message->flags_ & MESSAGE_FLAG_HAS_MEDIA_ALBUM_ID) { message_info.media_album_id = message->grouped_id_; } + if (message->flags_ & MESSAGE_FLAG_HAS_TTL_PERIOD) { + message_info.ttl_period = message->ttl_period_; + } message_info.flags = message->flags_; bool is_content_read = (message->flags_ & MESSAGE_FLAG_HAS_UNREAD_CONTENT) == 0; if (is_message_auto_read(message_info.dialog_id, (message->flags_ & MESSAGE_FLAG_IS_OUT) != 0)) { @@ -12740,6 +13473,9 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( message_info.sender_dialog_id = message_info.dialog_id; } message_info.date = message->date_; + if (message->flags_ & MESSAGE_FLAG_HAS_TTL_PERIOD) { + message_info.ttl_period = message->ttl_period_; + } message_info.flags = message->flags_; auto reply_to_message_id = MessageId(ServerMessageId(message->reply_to_ == nullptr ? 0 : message->reply_to_->reply_to_msg_id_)); @@ -12897,10 +13633,16 @@ std::pair> MessagesManager::creat hide_edit_date = false; } + int32 ttl_period = message_info.ttl_period; + if (ttl_period < 0 || message_id.is_scheduled()) { + LOG(ERROR) << "Wrong TTL period = " << ttl_period << " received in " << message_id << " in " << dialog_id; + ttl_period = 0; + } + int32 ttl = message_info.ttl; bool is_content_secret = is_secret_message_content(ttl, content_type); // should be calculated before TTL is adjusted - if (ttl < 0) { - LOG(ERROR) << "Wrong ttl = " << ttl << " received in " << message_id << " in " << dialog_id; + if (ttl < 0 || message_id.is_scheduled()) { + LOG(ERROR) << "Wrong TTL = " << ttl << " received in " << message_id << " in " << dialog_id; ttl = 0; } else if (ttl > 0) { ttl = max(ttl, get_message_content_duration(message_info.content.get(), td_) + 1); @@ -12934,6 +13676,7 @@ std::pair> MessagesManager::creat message->sender_user_id = sender_user_id; message->sender_dialog_id = sender_dialog_id; message->date = date; + message->ttl_period = ttl_period; message->ttl = ttl; message->edit_date = edit_date; message->random_id = message_info.random_id; @@ -13018,6 +13761,23 @@ MessageId MessagesManager::find_old_message_id(DialogId dialog_id, MessageId mes return MessageId(); } +void MessagesManager::delete_update_message_id(DialogId dialog_id, MessageId message_id) { + if (message_id.is_scheduled()) { + CHECK(message_id.is_scheduled_server()); + auto dialog_it = update_scheduled_message_ids_.find(dialog_id); + CHECK(dialog_it != update_scheduled_message_ids_.end()); + auto erased_count = dialog_it->second.erase(message_id.get_scheduled_server_message_id()); + CHECK(erased_count > 0); + if (dialog_it->second.empty()) { + update_scheduled_message_ids_.erase(dialog_it); + } + } else { + CHECK(message_id.is_server()); + auto erased_count = update_message_ids_.erase(FullMessageId(dialog_id, message_id)); + CHECK(erased_count > 0); + } +} + FullMessageId MessagesManager::on_get_message(tl_object_ptr message_ptr, bool from_update, bool is_channel_message, bool is_scheduled, bool have_previous, bool have_next, const char *source) { @@ -13043,7 +13803,9 @@ FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, bool f MessageId old_message_id = find_old_message_id(dialog_id, message_id); bool is_sent_message = false; - LOG(INFO) << "Found temporarily " << old_message_id << " for " << FullMessageId{dialog_id, message_id}; + if (old_message_id.is_valid()) { + LOG(INFO) << "Found temporary " << old_message_id << " for " << FullMessageId{dialog_id, message_id}; + } if (old_message_id.is_valid() || old_message_id.is_valid_scheduled()) { Dialog *d = get_dialog(dialog_id); if (d == nullptr) { @@ -13076,17 +13838,7 @@ FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, bool f } } - if (message_id.is_scheduled()) { - CHECK(message_id.is_scheduled_server()); - auto dialog_it = update_scheduled_message_ids_.find(dialog_id); - CHECK(dialog_it != update_scheduled_message_ids_.end()); - dialog_it->second.erase(message_id.get_scheduled_server_message_id()); - if (dialog_it->second.empty()) { - update_scheduled_message_ids_.erase(dialog_it); - } - } else { - update_message_ids_.erase(FullMessageId(dialog_id, message_id)); - } + delete_update_message_id(dialog_id, message_id); if (!new_message->is_outgoing && dialog_id != get_my_dialog_id()) { // sent message is not from me @@ -13181,7 +13933,9 @@ FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, bool f return FullMessageId(); } - send_update_chat_has_scheduled_messages(d, false); + if (m->message_id.is_scheduled()) { + send_update_chat_has_scheduled_messages(d, false); + } if (need_update_dialog_pos) { send_update_chat_last_message(d, "on_get_message"); @@ -13308,6 +14062,10 @@ void MessagesManager::set_dialog_last_clear_history_date(Dialog *d, int32 date, const char *source, bool is_loaded_from_database) { CHECK(!last_clear_history_message_id.is_scheduled()); + if (d->last_clear_history_message_id == last_clear_history_message_id && d->last_clear_history_date == date) { + return; + } + LOG(INFO) << "Set " << d->dialog_id << " last clear history date to " << date << " of " << last_clear_history_message_id << " from " << source; if (d->last_clear_history_message_id.is_valid()) { @@ -14516,11 +15274,16 @@ unique_ptr MessagesManager::do_delete_message(Dialog * if (*it != nullptr) { if (!(*it)->message_id.is_yet_unsent() && (*it)->message_id != d->last_database_message_id) { - set_dialog_last_database_message_id(d, (*it)->message_id, "do_delete_message"); - if (d->last_database_message_id < d->first_database_message_id) { - LOG(ERROR) << "Last database " << d->last_database_message_id << " became less than first database " - << d->first_database_message_id << " after deletion of " << full_message_id; - set_dialog_first_database_message_id(d, d->last_database_message_id, "do_delete_message 2"); + if ((*it)->message_id < d->first_database_message_id && d->dialog_id.get_type() == DialogType::Channel) { + // possible if messages was deleted from database, but not from memory after updateChannelTooLong + set_dialog_last_database_message_id(d, MessageId(), "do_delete_message"); + } else { + set_dialog_last_database_message_id(d, (*it)->message_id, "do_delete_message"); + if (d->last_database_message_id < d->first_database_message_id) { + LOG(ERROR) << "Last database " << d->last_database_message_id << " became less than first database " + << d->first_database_message_id << " after deletion of " << full_message_id; + set_dialog_first_database_message_id(d, d->last_database_message_id, "do_delete_message 2"); + } } } else { need_get_history = true; @@ -14677,7 +15440,8 @@ void MessagesManager::on_message_deleted(Dialog *d, Message *m, bool is_permanen default: UNREACHABLE(); } - ttl_unregister_message(d->dialog_id, m, Time::now(), source); + ttl_unregister_message(d->dialog_id, m, source); + ttl_period_unregister_message(d->dialog_id, m); unregister_message_content(td_, m->content.get(), {d->dialog_id, m->message_id}, "on_message_deleted"); if (m->notification_id.is_valid()) { delete_notification_id_to_message_id_correspondence(d, m->notification_id, m->message_id); @@ -17480,7 +18244,7 @@ void MessagesManager::reorder_dialog_filters_on_server(vector di send_closure(actor_id, &MessagesManager::on_reorder_dialog_filters, std::move(dialog_filter_ids), result.is_error() ? result.move_as_error() : Status::OK()); }); - td_->create_handler(std::move(promise))->send(std::move(dialog_filter_ids)); + td_->create_handler(std::move(promise))->send(std::move(dialog_filter_ids)); } void MessagesManager::on_reorder_dialog_filters(vector dialog_filter_ids, Status result) { @@ -18685,7 +19449,7 @@ void MessagesManager::create_dialog(DialogId dialog_id, bool force, Promise &user_ids, } DialogId MessagesManager::create_new_channel_chat(const string &title, bool is_megagroup, const string &description, - const DialogLocation &location, int64 &random_id, + const DialogLocation &location, bool for_import, int64 &random_id, Promise &&promise) { LOG(INFO) << "Trying to create " << (is_megagroup ? "supergroup" : "broadcast") << " with title \"" << title << "\", description \"" << description << "\" and " << location; @@ -18780,7 +19544,8 @@ DialogId MessagesManager::create_new_channel_chat(const string &title, bool is_m created_dialogs_[random_id]; // reserve place for result td_->create_handler(std::move(promise)) - ->send(new_title, is_megagroup, strip_empty_characters(description, MAX_DESCRIPTION_LENGTH), location, random_id); + ->send(new_title, is_megagroup, strip_empty_characters(description, MAX_DESCRIPTION_LENGTH), location, for_import, + random_id); return DialogId(); } @@ -19115,8 +19880,11 @@ void MessagesManager::open_dialog(Dialog *d) { } } - LOG(INFO) << "Cancel unload timeout for " << dialog_id; - pending_unload_dialog_timeout_.cancel_timeout(dialog_id.get()); + if (d->has_unload_timeout) { + LOG(INFO) << "Cancel unload timeout for " << dialog_id; + pending_unload_dialog_timeout_.cancel_timeout(dialog_id.get()); + d->has_unload_timeout = false; + } if (d->new_secret_chat_notification_id.is_valid()) { remove_new_secret_chat_notification(d, true); @@ -19139,10 +19907,9 @@ void MessagesManager::open_dialog(Dialog *d) { if (!is_broadcast_channel(dialog_id)) { auto participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id()); if (participant_count < 195) { // include unknown participant_count - td_->contacts_manager_->send_get_channel_participants_query( - dialog_id.get_channel_id(), - ChannelParticipantsFilter(td_api::make_object()), 0, 200, 0, - Auto()); + td_->contacts_manager_->get_channel_participants(dialog_id.get_channel_id(), + td_api::make_object(), + string(), 0, 200, 200, true, Auto()); } } get_channel_difference(dialog_id, d->pts, true, "open_dialog"); @@ -19219,6 +19986,8 @@ void MessagesManager::close_dialog(Dialog *d) { } if (is_message_unload_enabled()) { + CHECK(!d->has_unload_timeout); + d->has_unload_timeout = true; pending_unload_dialog_timeout_.set_timeout_in(dialog_id.get(), get_unload_dialog_delay()); } @@ -19307,9 +20076,14 @@ td_api::object_ptr MessagesManager::get_chat_action_bar_o if (d->can_report_location) { CHECK(d->dialog_id.get_type() == DialogType::Channel); - CHECK(!d->can_share_phone_number && !d->can_block_user && !d->can_add_contact && !d->can_report_spam); + CHECK(!d->can_share_phone_number && !d->can_block_user && !d->can_add_contact && !d->can_report_spam && + !d->can_invite_members); return td_api::make_object(); } + if (d->can_invite_members) { + CHECK(!d->can_share_phone_number && !d->can_block_user && !d->can_add_contact && !d->can_report_spam); + return td_api::make_object(); + } if (d->can_share_phone_number) { CHECK(d->dialog_id.get_type() == DialogType::User); CHECK(!d->can_block_user && !d->can_add_contact && !d->can_report_spam); @@ -19409,7 +20183,8 @@ td_api::object_ptr MessagesManager::get_chat_object(const Dialog * 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(), d->last_read_outbox_message_id.get(), d->unread_mention_count, - get_chat_notification_settings_object(&d->notification_settings), get_chat_action_bar_object(d), + get_chat_notification_settings_object(&d->notification_settings), + d->message_ttl_setting.get_message_ttl_setting_object(), get_chat_action_bar_object(d), active_group_call_id.get(), active_group_call_id.is_valid() ? d->is_group_call_empty : true, d->reply_markup_message_id.get(), std::move(draft_message), d->client_data); } @@ -20931,6 +21706,10 @@ void MessagesManager::on_message_live_location_viewed_on_server(int64 task_id) { } FileSourceId MessagesManager::get_message_file_source_id(FullMessageId full_message_id) { + if (td_->auth_manager_->is_bot()) { + return FileSourceId(); + } + auto dialog_id = full_message_id.get_dialog_id(); auto message_id = full_message_id.get_message_id(); if (!dialog_id.is_valid() || !(message_id.is_valid() || message_id.is_valid_scheduled()) || @@ -20946,6 +21725,10 @@ FileSourceId MessagesManager::get_message_file_source_id(FullMessageId full_mess } void MessagesManager::add_message_file_sources(DialogId dialog_id, const Message *m) { + if (td_->auth_manager_->is_bot()) { + return; + } + if (dialog_id.get_type() != DialogType::SecretChat && m->is_content_secret) { // return; } @@ -20965,6 +21748,10 @@ void MessagesManager::add_message_file_sources(DialogId dialog_id, const Message } void MessagesManager::remove_message_file_sources(DialogId dialog_id, const Message *m) { + if (td_->auth_manager_->is_bot()) { + return; + } + auto file_ids = get_message_content_file_ids(m->content.get(), td_); if (file_ids.empty()) { return; @@ -22256,6 +23043,10 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial } else { ttl_expires_in = m->ttl; } + if (ttl == 0 && m->ttl_period != 0) { + ttl = m->ttl_period; + ttl_expires_in = max(m->date + m->ttl_period - G()->server_time(), 1e-3); + } } else { ttl = 0; } @@ -22465,7 +23256,9 @@ MessagesManager::Message *MessagesManager::get_message_to_send( CHECK(have_input_peer(dialog_id, AccessRights::Read)); auto result = add_message_to_dialog(d, std::move(m), true, &need_update, need_update_dialog_pos, "send message"); LOG_CHECK(result != nullptr) << message_id << " " << debug_add_message_to_dialog_fail_reason_; - send_update_chat_has_scheduled_messages(d, false); + if (result->message_id.is_scheduled()) { + send_update_chat_has_scheduled_messages(d, false); + } return result; } @@ -23050,10 +23843,10 @@ Result MessagesManager::process_input_message_content( TRY_RESULT(content, get_input_message_content(dialog_id, std::move(input_message_content), td_)); if (content.ttl < 0 || content.ttl > MAX_PRIVATE_MESSAGE_TTL) { - return Status::Error(10, "Invalid message TTL specified"); + return Status::Error(10, "Invalid message content TTL specified"); } if (content.ttl > 0 && dialog_id.get_type() != DialogType::User) { - return Status::Error(10, "Message TTL can be specified only in private chats"); + return Status::Error(10, "Message content TTL can be specified only in private chats"); } if (dialog_id != DialogId()) { @@ -24355,7 +25148,7 @@ int32 MessagesManager::get_message_schedule_date(const Message *m) { DialogId MessagesManager::get_message_original_sender(const Message *m) { if (m->forward_info != nullptr) { auto forward_info = m->forward_info.get(); - if (is_forward_info_sender_hidden(forward_info)) { + if (forward_info->is_imported || is_forward_info_sender_hidden(forward_info)) { return DialogId(); } if (forward_info->message_id.is_valid() || forward_info->sender_dialog_id.is_valid()) { @@ -24868,7 +25661,7 @@ void MessagesManager::edit_inline_message_media(const string &inline_message_id, } InputMessageContent content = r_input_message_content.move_as_ok(); if (content.ttl > 0) { - LOG(ERROR) << "Have message content with ttl " << content.ttl; + LOG(ERROR) << "Have message content with TTL " << content.ttl; return promise.set_error(Status::Error(5, "Can't enable self-destruction for media")); } @@ -25306,10 +26099,14 @@ tl_object_ptr MessagesManager::get_game_high_scores_obje } bool MessagesManager::is_forward_info_sender_hidden(const MessageForwardInfo *forward_info) { + CHECK(forward_info != nullptr); + if (forward_info->is_imported) { + return false; + } if (!forward_info->sender_name.empty()) { return true; } - DialogId hidden_sender_dialog_id(static_cast(G()->is_test_dc() ? -1000010460537ll : -1001228946795ll)); + DialogId hidden_sender_dialog_id(ChannelId(G()->is_test_dc() ? 10460537 : 1228946795)); return forward_info->sender_dialog_id == hidden_sender_dialog_id && !forward_info->author_signature.empty() && !forward_info->message_id.is_valid(); } @@ -25332,6 +26129,7 @@ unique_ptr MessagesManager::get_message_for DialogId from_dialog_id; MessageId from_message_id; string sender_name = std::move(forward_header->from_name_); + bool is_imported = forward_header->imported_; if (forward_header->from_id_ != nullptr) { sender_dialog_id = DialogId(forward_header->from_id_); if (!sender_dialog_id.is_valid()) { @@ -25389,7 +26187,7 @@ unique_ptr MessagesManager::get_message_for return td::make_unique(sender_user_id, forward_header->date_, sender_dialog_id, message_id, std::move(author_signature), std::move(sender_name), from_dialog_id, - from_message_id, std::move(forward_header->psa_type_)); + from_message_id, std::move(forward_header->psa_type_), is_imported); } td_api::object_ptr MessagesManager::get_message_forward_info_object( @@ -25399,6 +26197,9 @@ td_api::object_ptr MessagesManager::get_message_forw } auto origin = [&]() -> td_api::object_ptr { + if (forward_info->is_imported) { + return td_api::make_object(forward_info->sender_name); + } if (is_forward_info_sender_hidden(forward_info.get())) { return td_api::make_object( forward_info->sender_name.empty() ? forward_info->author_signature : forward_info->sender_name); @@ -25735,14 +26536,15 @@ Result> MessagesManager::forward_messages(DialogId to_dialog_i : forwarded_message->author_signature; forward_info = td::make_unique( UserId(), forwarded_message->date, from_dialog_id, forwarded_message->message_id, - std::move(author_signature), "", saved_from_dialog_id, saved_from_message_id, ""); + std::move(author_signature), "", saved_from_dialog_id, saved_from_message_id, "", false); } else { LOG(ERROR) << "Don't know how to forward a channel post not from a channel"; } } else if (forwarded_message->sender_user_id.is_valid() || forwarded_message->sender_dialog_id.is_valid()) { forward_info = td::make_unique( forwarded_message->sender_user_id, forwarded_message->date, forwarded_message->sender_dialog_id, - MessageId(), "", forwarded_message->author_signature, saved_from_dialog_id, saved_from_message_id, ""); + MessageId(), "", forwarded_message->author_signature, saved_from_dialog_id, saved_from_message_id, "", + false); } else { LOG(ERROR) << "Don't know how to forward a non-channel post message without forward info and sender"; } @@ -25973,40 +26775,6 @@ Result> MessagesManager::resend_messages(DialogId dialog_id, v return result; } -Result MessagesManager::send_dialog_set_ttl_message(DialogId dialog_id, int32 ttl) { - if (dialog_id.get_type() != DialogType::SecretChat) { - return Status::Error(5, "Can't set chat ttl in non-secret chat"); - } - - if (ttl < 0) { - return Status::Error(5, "Message ttl can't be negative"); - } - - LOG(INFO) << "Begin to set ttl in " << dialog_id << " to " << ttl; - - Dialog *d = get_dialog_force(dialog_id); - if (d == nullptr) { - return Status::Error(5, "Chat not found"); - } - - TRY_STATUS(can_send_message(dialog_id)); - bool need_update_dialog_pos = false; - Message *m = get_message_to_send(d, MessageId(), MessageId(), MessageSendOptions(), - create_chat_set_ttl_message_content(ttl), &need_update_dialog_pos); - - send_update_new_message(d, m); - if (need_update_dialog_pos) { - send_update_chat_last_message(d, "send_dialog_set_ttl_message"); - } - - int64 random_id = begin_send_message(dialog_id, m); - - send_closure(td_->secret_chats_manager_, &SecretChatsManager::send_set_ttl_message, dialog_id.get_secret_chat_id(), - ttl, random_id, Promise<>()); // TODO Promise - - return m->message_id; -} - Status MessagesManager::send_screenshot_taken_notification_message(DialogId dialog_id) { auto dialog_type = dialog_id.get_type(); if (dialog_type != DialogType::User && dialog_type != DialogType::SecretChat) { @@ -26238,8 +27006,177 @@ Result MessagesManager::add_local_message( return message_id; } +void MessagesManager::get_message_file_type(const string &message_file_head, + Promise> &&promise) { + td_->create_handler(std::move(promise))->send(message_file_head); +} + +Status MessagesManager::can_import_messages(DialogId dialog_id) { + if (!have_dialog_force(dialog_id)) { + return Status::Error(400, "Chat not found"); + } + + TRY_STATUS(can_send_message(dialog_id)); + + switch (dialog_id.get_type()) { + case DialogType::User: + if (!td_->contacts_manager_->is_user_contact(dialog_id.get_user_id(), true)) { + return Status::Error(400, "User must be a mutual contact"); + } + break; + case DialogType::Chat: + return Status::Error(400, "Basic groups must be updagraded to supergroups first"); + case DialogType::Channel: + if (is_broadcast_channel(dialog_id)) { + return Status::Error(400, "Can't import messages to channels"); + } + if (!td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_change_info_and_settings()) { + return Status::Error(400, "Not enough rights to import messages"); + } + break; + case DialogType::SecretChat: + return Status::Error(400, "Can't import messages to secret chats"); + case DialogType::None: + default: + UNREACHABLE(); + } + + return Status::OK(); +} + +void MessagesManager::get_message_import_confirmation_text(DialogId dialog_id, Promise &&promise) { + TRY_STATUS_PROMISE(promise, can_import_messages(dialog_id)); + + td_->create_handler(std::move(promise))->send(dialog_id); +} + +void MessagesManager::import_messages(DialogId dialog_id, const td_api::object_ptr &message_file, + const vector> &attached_files, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, can_import_messages(dialog_id)); + + auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Document, message_file, dialog_id, false, false); + if (r_file_id.is_error()) { + // TODO TRY_RESULT_PROMISE(promise, ...); + return promise.set_error(Status::Error(400, r_file_id.error().message())); + } + FileId file_id = r_file_id.ok(); + + vector attached_file_ids; + attached_file_ids.reserve(attached_files.size()); + for (auto &attached_file : attached_files) { + auto file_type = td_->file_manager_->guess_file_type(attached_file); + if (file_type != FileType::Animation && file_type != FileType::Audio && file_type != FileType::Document && + file_type != FileType::Photo && file_type != FileType::Sticker && file_type != FileType::Video && + file_type != FileType::VoiceNote) { + LOG(INFO) << "Skip attached file of type " << file_type; + continue; + } + auto r_attached_file_id = td_->file_manager_->get_input_file_id(file_type, attached_file, dialog_id, false, false); + if (r_attached_file_id.is_error()) { + // TODO TRY_RESULT_PROMISE(promise, ...); + return promise.set_error(Status::Error(400, r_attached_file_id.error().message())); + } + attached_file_ids.push_back(r_attached_file_id.ok()); + } + + upload_imported_messages(dialog_id, td_->file_manager_->dup_file_id(file_id), std::move(attached_file_ids), false, + std::move(promise)); +} + +void MessagesManager::upload_imported_messages(DialogId dialog_id, FileId file_id, vector attached_file_ids, + bool is_reupload, Promise &&promise, vector bad_parts) { + CHECK(file_id.is_valid()); + LOG(INFO) << "Ask to upload imported messages file " << file_id; + CHECK(being_uploaded_imported_messages_.find(file_id) == being_uploaded_imported_messages_.end()); + being_uploaded_imported_messages_.emplace( + file_id, td::make_unique(dialog_id, std::move(attached_file_ids), is_reupload, + std::move(promise))); + // TODO use force_reupload if is_reupload + td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_imported_messages_callback_, 1, 0, false, + true); +} + +void MessagesManager::start_import_messages(DialogId dialog_id, int64 import_id, vector &&attached_file_ids, + Promise &&promise) { + if (G()->close_flag()) { + return promise.set_error(Status::Error(500, "Request aborted")); + } + + TRY_STATUS_PROMISE(promise, can_send_message(dialog_id)); + + auto pending_message_import = make_unique(); + pending_message_import->dialog_id = dialog_id; + pending_message_import->import_id = import_id; + pending_message_import->promise = std::move(promise); + + auto &multipromise = pending_message_import->upload_files_multipromise; + + int64 random_id; + do { + random_id = Random::secure_int64(); + } while (random_id == 0 || pending_message_imports_.find(random_id) != pending_message_imports_.end()); + pending_message_imports_[random_id] = std::move(pending_message_import); + + multipromise.add_promise(PromiseCreator::lambda([random_id](Result result) { + send_closure_later(G()->messages_manager(), &MessagesManager::on_imported_message_attachments_uploaded, random_id, + std::move(result)); + })); + auto lock_promise = multipromise.get_promise(); + + for (auto attached_file_id : attached_file_ids) { + upload_imported_message_attachment(dialog_id, import_id, td_->file_manager_->dup_file_id(attached_file_id), false, + multipromise.get_promise()); + } + + lock_promise.set_value(Unit()); +} + +void MessagesManager::upload_imported_message_attachment(DialogId dialog_id, int64 import_id, FileId file_id, + bool is_reupload, Promise &&promise, + vector bad_parts) { + CHECK(file_id.is_valid()); + LOG(INFO) << "Ask to upload improted message attached file " << file_id; + CHECK(being_uploaded_imported_message_attachments_.find(file_id) == + being_uploaded_imported_message_attachments_.end()); + being_uploaded_imported_message_attachments_.emplace( + file_id, + td::make_unique(dialog_id, import_id, is_reupload, std::move(promise))); + // TODO use force_reupload if is_reupload + td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_imported_message_attachment_callback_, 1, 0, + false, true); +} + +void MessagesManager::on_imported_message_attachments_uploaded(int64 random_id, Result &&result) { + if (G()->close_flag()) { + result = Status::Error(500, "Request aborted"); + } + + auto it = pending_message_imports_.find(random_id); + CHECK(it != pending_message_imports_.end()); + + auto pending_message_import = std::move(it->second); + CHECK(pending_message_import != nullptr); + + pending_message_imports_.erase(it); + + if (result.is_error()) { + pending_message_import->promise.set_error(result.move_as_error()); + return; + } + + CHECK(pending_message_import->upload_files_multipromise.promise_count() == 0); + + auto promise = std::move(pending_message_import->promise); + auto dialog_id = pending_message_import->dialog_id; + + TRY_STATUS_PROMISE(promise, can_send_message(dialog_id)); + + td_->create_handler(std::move(promise))->send(dialog_id, pending_message_import->import_id); +} + bool MessagesManager::on_update_message_id(int64 random_id, MessageId new_message_id, const string &source) { - if (!new_message_id.is_valid()) { + if (!new_message_id.is_valid() || !new_message_id.is_server()) { LOG(ERROR) << "Receive " << new_message_id << " in updateMessageId with random_id " << random_id << " from " << source; return false; @@ -26247,7 +27184,7 @@ bool MessagesManager::on_update_message_id(int64 random_id, MessageId new_messag auto it = being_sent_messages_.find(random_id); if (it == being_sent_messages_.end()) { - // update about new message sent from other device or service message + // update about a new message sent from other device or a service message LOG(INFO) << "Receive not send outgoing " << new_message_id << " with random_id = " << random_id; return true; } @@ -27227,6 +28164,9 @@ bool MessagesManager::is_message_notification_disabled(const Dialog *d, const Me G()->shared_config().get_option_boolean("disable_sent_scheduled_message_notifications")) { return true; } + if (m->forward_info != nullptr && m->forward_info->is_imported) { + return true; + } switch (m->content->get_type()) { case MessageContentType::ChatDeleteHistory: @@ -27408,7 +28348,7 @@ bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool f if (settings_dialog != nullptr) { send_get_dialog_notification_settings_query(settings_dialog_id, std::move(promise)); } else { - send_get_dialog_query(settings_dialog_id, std::move(promise)); + send_get_dialog_query(settings_dialog_id, std::move(promise), 0, "add_new_message_notification"); } } if (missing_pinned_message_id.is_valid()) { @@ -27931,10 +28871,6 @@ void MessagesManager::send_update_chat_action_bar(const Dialog *d) { } void MessagesManager::send_update_chat_voice_chat(const Dialog *d) { - if (td_->auth_manager_->is_bot()) { - return; - } - CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_voice_chat"; on_dialog_updated(d->dialog_id, "send_update_chat_voice_chat"); @@ -27944,6 +28880,15 @@ void MessagesManager::send_update_chat_voice_chat(const Dialog *d) { d->is_group_call_empty)); } +void MessagesManager::send_update_chat_message_ttl_setting(const Dialog *d) { + CHECK(d != nullptr); + LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_message_ttl_setting"; + on_dialog_updated(d->dialog_id, "send_update_chat_message_ttl_setting"); + send_closure(G()->td(), &Td::send_update, + td_api::make_object( + d->dialog_id.get(), d->message_ttl_setting.get_message_ttl_setting_object())); +} + void MessagesManager::send_update_chat_has_scheduled_messages(Dialog *d, bool from_deletion) { if (td_->auth_manager_->is_bot()) { return; @@ -28037,7 +28982,7 @@ void MessagesManager::check_send_message_result(int64 random_id, DialogId dialog } FullMessageId MessagesManager::on_send_message_success(int64 random_id, MessageId new_message_id, int32 date, - FileId new_file_id, const char *source) { + int32 ttl_period, FileId new_file_id, const char *source) { CHECK(source != nullptr); // do not try to run getDifference from this function if (DROP_SEND_MESSAGE_UPDATES) { @@ -28110,6 +29055,8 @@ FullMessageId MessagesManager::on_send_message_success(int64 random_id, MessageI CHECK(d->last_message_id != old_message_id); } + sent_message->ttl_period = ttl_period; + // reply_to message may be already deleted // but can't use get_message_force for check, because the message can be already unloaded from the memory // if (get_message_force(d, sent_message->reply_to_message_id, "on_send_message_success 2") == nullptr) { @@ -28650,7 +29597,7 @@ void MessagesManager::on_update_dialog_draft_message(DialogId dialog_id, if (!have_input_peer(dialog_id, AccessRights::Read)) { LOG(ERROR) << "Have no read access to " << dialog_id << " to repair chat draft message"; } else { - send_get_dialog_query(dialog_id, Promise()); + send_get_dialog_query(dialog_id, Promise(), 0, "on_update_dialog_draft_message"); } return; } @@ -29109,10 +30056,6 @@ void MessagesManager::do_set_dialog_folder_id(Dialog *d, FolderId folder_id) { void MessagesManager::on_update_dialog_group_call(DialogId dialog_id, bool has_active_group_call, bool is_group_call_empty, const char *source) { - if (td_->auth_manager_->is_bot()) { - return; - } - LOG(INFO) << "Update voice chat in " << dialog_id << " with has_active_voice_chat = " << has_active_group_call << " and is_voice_chat_empty = " << is_group_call_empty << " from " << source; @@ -29128,7 +30071,6 @@ void MessagesManager::on_update_dialog_group_call(DialogId dialog_id, bool has_a is_group_call_empty = false; } if (d->has_active_group_call == has_active_group_call && d->is_group_call_empty == is_group_call_empty) { - LOG(INFO) << "Nothing changed in " << dialog_id; return; } @@ -29152,10 +30094,6 @@ void MessagesManager::on_update_dialog_group_call(DialogId dialog_id, bool has_a } void MessagesManager::on_update_dialog_group_call_id(DialogId dialog_id, InputGroupCallId input_group_call_id) { - if (td_->auth_manager_->is_bot()) { - return; - } - auto d = get_dialog_force(dialog_id); if (d == nullptr) { // nothing to do @@ -29177,6 +30115,24 @@ void MessagesManager::on_update_dialog_group_call_id(DialogId dialog_id, InputGr } } +void MessagesManager::on_update_dialog_message_ttl_setting(DialogId dialog_id, MessageTtlSetting message_ttl_setting) { + auto d = get_dialog_force(dialog_id); + if (d == nullptr) { + // nothing to do + return; + } + + if (d->message_ttl_setting != message_ttl_setting) { + d->message_ttl_setting = message_ttl_setting; + d->is_message_ttl_setting_inited = true; + send_update_chat_message_ttl_setting(d); + } + if (!d->is_message_ttl_setting_inited) { + d->is_message_ttl_setting_inited = true; + on_dialog_updated(dialog_id, "on_update_dialog_message_ttl_setting"); + } +} + void MessagesManager::on_update_dialog_filters() { if (td_->auth_manager_->is_bot()) { // just in case @@ -29243,12 +30199,12 @@ void MessagesManager::on_create_new_dialog_fail(int64 random_id, Status error, P td_->updates_manager_->get_difference("on_create_new_dialog_fail"); } -void MessagesManager::on_dialog_bots_updated(DialogId dialog_id, vector bot_user_ids) { +void MessagesManager::on_dialog_bots_updated(DialogId dialog_id, vector bot_user_ids, bool from_database) { if (td_->auth_manager_->is_bot()) { return; } - auto d = get_dialog_force(dialog_id); + auto d = from_database ? get_dialog(dialog_id) : get_dialog_force(dialog_id); if (d == nullptr || d->reply_markup_message_id == MessageId()) { return; } @@ -29423,7 +30379,7 @@ DialogId MessagesManager::search_public_dialog(const string &username_to_search, } else { const Dialog *d = get_dialog_force(dialog_id); if (!is_dialog_inited(d)) { - send_get_dialog_query(dialog_id, std::move(promise)); + send_get_dialog_query(dialog_id, std::move(promise), 0, "search_public_dialog"); return DialogId(); } } @@ -29513,7 +30469,8 @@ uint64 MessagesManager::save_get_dialog_from_server_log_event(DialogId dialog_id get_log_event_storer(log_event)); } -void MessagesManager::send_get_dialog_query(DialogId dialog_id, Promise &&promise, uint64 log_event_id) { +void MessagesManager::send_get_dialog_query(DialogId dialog_id, Promise &&promise, uint64 log_event_id, + const char *source) { if (td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) { if (log_event_id != 0) { binlog_erase(G()->td_db()->get_binlog(), log_event_id); @@ -29531,7 +30488,7 @@ void MessagesManager::send_get_dialog_query(DialogId dialog_id, Promise && promises.push_back(std::move(promise)); if (promises.size() != 1) { if (log_event_id != 0) { - LOG(INFO) << "Duplicate getDialog query for " << dialog_id; + LOG(INFO) << "Duplicate getDialog query for " << dialog_id << " from " << source; binlog_erase(G()->td_db()->get_binlog(), log_event_id); } // query has already been sent, just wait for the result @@ -29550,7 +30507,7 @@ void MessagesManager::send_get_dialog_query(DialogId dialog_id, Promise && return; } - LOG(INFO) << "Send get " << dialog_id << " query"; + LOG(INFO) << "Send get " << dialog_id << " query from " << source; td_->create_handler()->send(dialog_id); } @@ -29644,7 +30601,7 @@ void MessagesManager::drop_username(const string &username) { auto dialog_id = it->second.dialog_id; if (have_input_peer(dialog_id, AccessRights::Read)) { CHECK(dialog_id.get_type() != DialogType::SecretChat); - send_get_dialog_query(dialog_id, Auto()); + send_get_dialog_query(dialog_id, Auto(), 0, "drop_username"); } resolved_usernames_.erase(it); @@ -30230,6 +31187,72 @@ void MessagesManager::set_dialog_title(DialogId dialog_id, const string &title, td_->create_handler(std::move(promise))->send(dialog_id, new_title); } +void MessagesManager::set_dialog_message_ttl_setting(DialogId dialog_id, int32 ttl, Promise &&promise) { + if (ttl < 0) { + return promise.set_error(Status::Error(400, "Message auto-delete time can't be negative")); + } + + Dialog *d = get_dialog_force(dialog_id); + if (d == nullptr) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + if (!have_input_peer(dialog_id, AccessRights::Write)) { + return promise.set_error(Status::Error(400, "Have no write access to the chat")); + } + + LOG(INFO) << "Begin to set message TTL in " << dialog_id << " to " << ttl; + + switch (dialog_id.get_type()) { + case DialogType::User: + if (dialog_id == get_my_dialog_id() || + dialog_id == DialogId(ContactsManager::get_service_notifications_user_id())) { + return promise.set_error(Status::Error(400, "Message auto-delete time in the chat can't be changed")); + } + break; + case DialogType::Chat: { + auto chat_id = dialog_id.get_chat_id(); + auto status = td_->contacts_manager_->get_chat_permissions(chat_id); + if (!status.can_delete_messages()) { + return promise.set_error( + Status::Error(400, "Not enough rights to change message auto-delete time in the chat")); + } + break; + } + case DialogType::Channel: { + auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); + if (!status.can_change_info_and_settings()) { + return promise.set_error( + Status::Error(400, "Not enough rights to change message auto-delete time in the chat")); + } + break; + } + case DialogType::SecretChat: + break; + case DialogType::None: + default: + UNREACHABLE(); + } + + if (dialog_id.get_type() != DialogType::SecretChat) { + // TODO invoke after + td_->create_handler(std::move(promise))->send(dialog_id, ttl); + } else { + bool need_update_dialog_pos = false; + Message *m = get_message_to_send(d, MessageId(), MessageId(), MessageSendOptions(), + create_chat_set_ttl_message_content(ttl), &need_update_dialog_pos); + + send_update_new_message(d, m); + if (need_update_dialog_pos) { + send_update_chat_last_message(d, "set_dialog_message_ttl_setting"); + } + + int64 random_id = begin_send_message(dialog_id, m); + + send_closure(td_->secret_chats_manager_, &SecretChatsManager::send_set_ttl_message, dialog_id.get_secret_chat_id(), + ttl, random_id, std::move(promise)); + } +} + void MessagesManager::set_dialog_permissions(DialogId dialog_id, const td_api::object_ptr &permissions, Promise &&promise) { @@ -30283,7 +31306,7 @@ void MessagesManager::set_dialog_permissions(DialogId dialog_id, } // TODO invoke after - td_->create_handler(std::move(promise))->send(dialog_id, new_permissions); + td_->create_handler(std::move(promise))->send(dialog_id, new_permissions); } void MessagesManager::set_dialog_description(DialogId dialog_id, const string &description, Promise &&promise) { @@ -30437,322 +31460,6 @@ void MessagesManager::unpin_all_dialog_messages_on_server(DialogId dialog_id, ui ->send(dialog_id); } -void MessagesManager::add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit, - Promise &&promise) { - LOG(INFO) << "Receive AddChatParticipant request to add " << user_id << " to " << dialog_id; - if (!have_dialog_force(dialog_id)) { - return promise.set_error(Status::Error(3, "Chat not found")); - } - - switch (dialog_id.get_type()) { - case DialogType::User: - return promise.set_error(Status::Error(3, "Can't add members to a private chat")); - case DialogType::Chat: - return td_->contacts_manager_->add_chat_participant(dialog_id.get_chat_id(), user_id, forward_limit, - std::move(promise)); - case DialogType::Channel: - return td_->contacts_manager_->add_channel_participant(dialog_id.get_channel_id(), user_id, std::move(promise)); - case DialogType::SecretChat: - return promise.set_error(Status::Error(3, "Can't add members to a secret chat")); - case DialogType::None: - default: - UNREACHABLE(); - } -} - -void MessagesManager::add_dialog_participants(DialogId dialog_id, const vector &user_ids, - Promise &&promise) { - LOG(INFO) << "Receive AddChatParticipants request to add " << format::as_array(user_ids) << " to " << dialog_id; - if (td_->auth_manager_->is_bot()) { - return promise.set_error(Status::Error(3, "Method is not available for bots")); - } - - if (!have_dialog_force(dialog_id)) { - return promise.set_error(Status::Error(3, "Chat not found")); - } - - switch (dialog_id.get_type()) { - case DialogType::User: - return promise.set_error(Status::Error(3, "Can't add members to a private chat")); - case DialogType::Chat: - return promise.set_error(Status::Error(3, "Can't add many members at once to a basic group chat")); - case DialogType::Channel: - return td_->contacts_manager_->add_channel_participants(dialog_id.get_channel_id(), user_ids, std::move(promise)); - case DialogType::SecretChat: - return promise.set_error(Status::Error(3, "Can't add members to a secret chat")); - case DialogType::None: - default: - UNREACHABLE(); - } -} - -void MessagesManager::set_dialog_participant_status(DialogId dialog_id, UserId user_id, - const tl_object_ptr &chat_member_status, - Promise &&promise) { - auto status = get_dialog_participant_status(chat_member_status); - LOG(INFO) << "Receive setChatMemberStatus request with " << user_id << " and " << dialog_id << " to " << status; - if (!have_dialog_force(dialog_id)) { - return promise.set_error(Status::Error(3, "Chat not found")); - } - - switch (dialog_id.get_type()) { - case DialogType::User: - return promise.set_error(Status::Error(3, "Chat member status can't be changed in private chats")); - case DialogType::Chat: - return td_->contacts_manager_->change_chat_participant_status(dialog_id.get_chat_id(), user_id, status, - std::move(promise)); - case DialogType::Channel: - return td_->contacts_manager_->change_channel_participant_status(dialog_id.get_channel_id(), user_id, status, - std::move(promise)); - case DialogType::SecretChat: - return promise.set_error(Status::Error(3, "Chat member status can't be changed in secret chats")); - case DialogType::None: - default: - UNREACHABLE(); - } -} - -DialogParticipant MessagesManager::get_dialog_participant(DialogId dialog_id, UserId user_id, int64 &random_id, - bool force, Promise &&promise) { - LOG(INFO) << "Receive GetChatMember request to get " << user_id << " in " << dialog_id << " with random_id " - << random_id; - if (!have_dialog_force(dialog_id)) { - promise.set_error(Status::Error(3, "Chat not found")); - return DialogParticipant(); - } - - switch (dialog_id.get_type()) { - case DialogType::User: { - auto peer_user_id = dialog_id.get_user_id(); - if (user_id == td_->contacts_manager_->get_my_id()) { - promise.set_value(Unit()); - return {user_id, peer_user_id, 0, DialogParticipantStatus::Member()}; - } - if (user_id == peer_user_id) { - promise.set_value(Unit()); - return {peer_user_id, user_id, 0, DialogParticipantStatus::Member()}; - } - - promise.set_error(Status::Error(3, "User is not a member of the private chat")); - break; - } - case DialogType::Chat: - return td_->contacts_manager_->get_chat_participant(dialog_id.get_chat_id(), user_id, force, std::move(promise)); - case DialogType::Channel: - return td_->contacts_manager_->get_channel_participant(dialog_id.get_channel_id(), user_id, random_id, force, - std::move(promise)); - case DialogType::SecretChat: { - auto peer_user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); - if (user_id == td_->contacts_manager_->get_my_id()) { - promise.set_value(Unit()); - return {user_id, peer_user_id.is_valid() ? peer_user_id : user_id, 0, DialogParticipantStatus::Member()}; - } - if (peer_user_id.is_valid() && user_id == peer_user_id) { - promise.set_value(Unit()); - return {peer_user_id, user_id, 0, DialogParticipantStatus::Member()}; - } - - promise.set_error(Status::Error(3, "User is not a member of the secret chat")); - break; - } - case DialogType::None: - default: - UNREACHABLE(); - promise.set_error(Status::Error(500, "Wrong chat type")); - } - return DialogParticipant(); -} - -std::pair> MessagesManager::search_private_chat_participants( - UserId my_user_id, UserId peer_user_id, const string &query, int32 limit, DialogParticipantsFilter filter) const { - vector user_ids; - switch (filter.type) { - case DialogParticipantsFilter::Type::Contacts: - if (peer_user_id.is_valid() && td_->contacts_manager_->is_user_contact(peer_user_id)) { - user_ids.push_back(peer_user_id); - } - break; - case DialogParticipantsFilter::Type::Administrators: - break; - case DialogParticipantsFilter::Type::Members: - case DialogParticipantsFilter::Type::Mention: - user_ids.push_back(my_user_id); - if (peer_user_id.is_valid() && peer_user_id != my_user_id) { - user_ids.push_back(peer_user_id); - } - break; - case DialogParticipantsFilter::Type::Restricted: - break; - case DialogParticipantsFilter::Type::Banned: - break; - case DialogParticipantsFilter::Type::Bots: - if (td_->auth_manager_->is_bot()) { - user_ids.push_back(my_user_id); - } - if (peer_user_id.is_valid() && td_->contacts_manager_->is_user_bot(peer_user_id) && peer_user_id != my_user_id) { - user_ids.push_back(peer_user_id); - } - break; - default: - UNREACHABLE(); - } - - auto result = td_->contacts_manager_->search_among_users(user_ids, query, limit); - return {result.first, transform(result.second, [&](UserId user_id) { - return DialogParticipant(user_id, - user_id == my_user_id && peer_user_id.is_valid() ? peer_user_id : my_user_id, 0, - DialogParticipantStatus::Member()); - })}; -} - -std::pair> MessagesManager::search_dialog_participants( - DialogId dialog_id, const string &query, int32 limit, DialogParticipantsFilter filter, int64 &random_id, - bool without_bot_info, bool force, Promise &&promise) { - LOG(INFO) << "Receive searchChatMembers request to search for \"" << query << "\" in " << dialog_id << " with filter " - << filter; - if (!have_dialog_force(dialog_id)) { - promise.set_error(Status::Error(3, "Chat not found")); - return {}; - } - if (limit < 0) { - promise.set_error(Status::Error(3, "Parameter limit must be non-negative")); - return {}; - } - - switch (dialog_id.get_type()) { - case DialogType::User: - promise.set_value(Unit()); - return search_private_chat_participants(td_->contacts_manager_->get_my_id(), dialog_id.get_user_id(), query, - limit, filter); - case DialogType::Chat: - return td_->contacts_manager_->search_chat_participants(dialog_id.get_chat_id(), query, limit, filter, force, - std::move(promise)); - case DialogType::Channel: { - tl_object_ptr request_filter; - string additional_query; - int32 additional_limit = 0; - switch (filter.type) { - case DialogParticipantsFilter::Type::Contacts: - request_filter = td_api::make_object(); - break; - case DialogParticipantsFilter::Type::Administrators: - request_filter = td_api::make_object(); - break; - case DialogParticipantsFilter::Type::Members: - request_filter = td_api::make_object(query); - break; - case DialogParticipantsFilter::Type::Restricted: - request_filter = td_api::make_object(query); - break; - case DialogParticipantsFilter::Type::Banned: - request_filter = td_api::make_object(query); - break; - case DialogParticipantsFilter::Type::Mention: - request_filter = - td_api::make_object(query, filter.top_thread_message_id.get()); - break; - case DialogParticipantsFilter::Type::Bots: - request_filter = td_api::make_object(); - break; - default: - UNREACHABLE(); - } - switch (filter.type) { - case DialogParticipantsFilter::Type::Contacts: - case DialogParticipantsFilter::Type::Administrators: - case DialogParticipantsFilter::Type::Bots: - additional_query = query; - additional_limit = limit; - limit = 100; - break; - case DialogParticipantsFilter::Type::Members: - case DialogParticipantsFilter::Type::Restricted: - case DialogParticipantsFilter::Type::Banned: - case DialogParticipantsFilter::Type::Mention: - // query is passed to the server request - break; - default: - UNREACHABLE(); - } - - return td_->contacts_manager_->get_channel_participants(dialog_id.get_channel_id(), request_filter, - additional_query, 0, limit, additional_limit, random_id, - without_bot_info, force, std::move(promise)); - } - case DialogType::SecretChat: { - promise.set_value(Unit()); - auto peer_user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); - return search_private_chat_participants(td_->contacts_manager_->get_my_id(), peer_user_id, query, limit, filter); - } - case DialogType::None: - default: - UNREACHABLE(); - promise.set_error(Status::Error(500, "Wrong chat type")); - } - return {}; -} - -vector MessagesManager::get_dialog_administrators(DialogId dialog_id, int left_tries, - Promise &&promise) { - LOG(INFO) << "Receive GetChatAdministrators request in " << dialog_id; - if (!have_dialog_force(dialog_id)) { - promise.set_error(Status::Error(3, "Chat not found")); - return {}; - } - - switch (dialog_id.get_type()) { - case DialogType::User: - case DialogType::SecretChat: - promise.set_value(Unit()); - break; - case DialogType::Chat: - case DialogType::Channel: - return td_->contacts_manager_->get_dialog_administrators(dialog_id, left_tries, std::move(promise)); - case DialogType::None: - default: - UNREACHABLE(); - promise.set_error(Status::Error(500, "Wrong chat type")); - } - return {}; -} - -void MessagesManager::export_dialog_invite_link(DialogId dialog_id, Promise &&promise) { - LOG(INFO) << "Receive ExportDialogInviteLink request for " << dialog_id; - if (!have_dialog_force(dialog_id)) { - return promise.set_error(Status::Error(3, "Chat not found")); - } - - switch (dialog_id.get_type()) { - case DialogType::User: - return promise.set_error(Status::Error(3, "Can't invite members to a private chat")); - case DialogType::Chat: - return td_->contacts_manager_->export_chat_invite_link(dialog_id.get_chat_id(), std::move(promise)); - case DialogType::Channel: - return td_->contacts_manager_->export_channel_invite_link(dialog_id.get_channel_id(), std::move(promise)); - case DialogType::SecretChat: - return promise.set_error(Status::Error(3, "Can't invite members to a secret chat")); - case DialogType::None: - default: - UNREACHABLE(); - } -} - -string MessagesManager::get_dialog_invite_link(DialogId dialog_id) { - switch (dialog_id.get_type()) { - case DialogType::Chat: - return td_->contacts_manager_->get_chat_invite_link(dialog_id.get_chat_id()); - case DialogType::Channel: - return td_->contacts_manager_->get_channel_invite_link(dialog_id.get_channel_id()); - case DialogType::User: - case DialogType::SecretChat: - case DialogType::None: - return string(); - default: - UNREACHABLE(); - return string(); - } -} - tl_object_ptr MessagesManager::get_channel_admin_log_events_filter( const tl_object_ptr &filters) { if (filters == nullptr) { @@ -30794,14 +31501,17 @@ tl_object_ptr MessagesManager::get_ch if (filters->setting_changes_) { flags |= telegram_api::channelAdminLogEventsFilter::SETTINGS_MASK; } - if (filters->voice_chat_changes_) { + if (filters->invite_link_changes_) { flags |= telegram_api::channelAdminLogEventsFilter::GROUP_CALL_MASK; } + if (filters->voice_chat_changes_) { + flags |= telegram_api::channelAdminLogEventsFilter::INVITES_MASK; + } return make_tl_object( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/); + false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/); } int64 MessagesManager::get_dialog_event_log(DialogId dialog_id, const string &query, int64 from_event_id, int32 limit, @@ -30862,52 +31572,62 @@ tl_object_ptr MessagesManager::get_chat_event_action_ob switch (action_ptr->get_id()) { case telegram_api::channelAdminLogEventActionParticipantJoin::ID: return make_tl_object(); + case telegram_api::channelAdminLogEventActionParticipantJoinByInvite::ID: { + auto action = move_tl_object_as(action_ptr); + DialogInviteLink invite_link(std::move(action->invite_)); + if (!invite_link.is_valid()) { + LOG(ERROR) << "Wrong invite link: " << invite_link; + return nullptr; + } + return make_tl_object( + invite_link.get_chat_invite_link_object(td_->contacts_manager_.get())); + } case telegram_api::channelAdminLogEventActionParticipantLeave::ID: return make_tl_object(); case telegram_api::channelAdminLogEventActionParticipantInvite::ID: { auto action = move_tl_object_as(action_ptr); - auto member = td_->contacts_manager_->get_dialog_participant(channel_id, std::move(action->participant_)); - if (!member.is_valid()) { - LOG(ERROR) << "Wrong invite: " << member; + DialogParticipant dialog_participant(std::move(action->participant_)); + if (!dialog_participant.is_valid()) { + LOG(ERROR) << "Wrong invite: " << dialog_participant; return nullptr; } return make_tl_object( - td_->contacts_manager_->get_user_id_object(member.user_id, "chatEventMemberInvited"), - member.status.get_chat_member_status_object()); + td_->contacts_manager_->get_user_id_object(dialog_participant.user_id, "chatEventMemberInvited"), + dialog_participant.status.get_chat_member_status_object()); } case telegram_api::channelAdminLogEventActionParticipantToggleBan::ID: { auto action = move_tl_object_as(action_ptr); - auto old_member = - td_->contacts_manager_->get_dialog_participant(channel_id, std::move(action->prev_participant_)); - auto new_member = td_->contacts_manager_->get_dialog_participant(channel_id, std::move(action->new_participant_)); - if (old_member.user_id != new_member.user_id) { - LOG(ERROR) << old_member.user_id << " VS " << new_member.user_id; + DialogParticipant old_dialog_participant(std::move(action->prev_participant_)); + DialogParticipant new_dialog_participant(std::move(action->new_participant_)); + if (old_dialog_participant.user_id != new_dialog_participant.user_id) { + LOG(ERROR) << old_dialog_participant.user_id << " VS " << new_dialog_participant.user_id; return nullptr; } - if (!old_member.is_valid() || !new_member.is_valid()) { - LOG(ERROR) << "Wrong restrict: " << old_member << " -> " << new_member; + if (!old_dialog_participant.is_valid() || !new_dialog_participant.is_valid()) { + LOG(ERROR) << "Wrong restrict: " << old_dialog_participant << " -> " << new_dialog_participant; return nullptr; } return make_tl_object( - td_->contacts_manager_->get_user_id_object(old_member.user_id, "chatEventMemberRestricted"), - old_member.status.get_chat_member_status_object(), new_member.status.get_chat_member_status_object()); + td_->contacts_manager_->get_user_id_object(old_dialog_participant.user_id, "chatEventMemberRestricted"), + old_dialog_participant.status.get_chat_member_status_object(), + new_dialog_participant.status.get_chat_member_status_object()); } case telegram_api::channelAdminLogEventActionParticipantToggleAdmin::ID: { auto action = move_tl_object_as(action_ptr); - auto old_member = - td_->contacts_manager_->get_dialog_participant(channel_id, std::move(action->prev_participant_)); - auto new_member = td_->contacts_manager_->get_dialog_participant(channel_id, std::move(action->new_participant_)); - if (old_member.user_id != new_member.user_id) { - LOG(ERROR) << old_member.user_id << " VS " << new_member.user_id; + DialogParticipant old_dialog_participant(std::move(action->prev_participant_)); + DialogParticipant new_dialog_participant(std::move(action->new_participant_)); + if (old_dialog_participant.user_id != new_dialog_participant.user_id) { + LOG(ERROR) << old_dialog_participant.user_id << " VS " << new_dialog_participant.user_id; return nullptr; } - if (!old_member.is_valid() || !new_member.is_valid()) { - LOG(ERROR) << "Wrong edit administrator: " << old_member << " -> " << new_member; + if (!old_dialog_participant.is_valid() || !new_dialog_participant.is_valid()) { + LOG(ERROR) << "Wrong edit administrator: " << old_dialog_participant << " -> " << new_dialog_participant; return nullptr; } return make_tl_object( - td_->contacts_manager_->get_user_id_object(old_member.user_id, "chatEventMemberPromoted"), - old_member.status.get_chat_member_status_object(), new_member.status.get_chat_member_status_object()); + td_->contacts_manager_->get_user_id_object(old_dialog_participant.user_id, "chatEventMemberPromoted"), + old_dialog_participant.status.get_chat_member_status_object(), + new_dialog_participant.status.get_chat_member_status_object()); } case telegram_api::channelAdminLogEventActionChangeTitle::ID: { auto action = move_tl_object_as(action_ptr); @@ -31051,6 +31771,38 @@ tl_object_ptr MessagesManager::get_chat_event_action_ob auto new_slow_mode_delay = clamp(action->new_value_, 0, 86400 * 366); return make_tl_object(old_slow_mode_delay, new_slow_mode_delay); } + case telegram_api::channelAdminLogEventActionExportedInviteEdit::ID: { + auto action = move_tl_object_as(action_ptr); + DialogInviteLink old_invite_link(std::move(action->prev_invite_)); + DialogInviteLink new_invite_link(std::move(action->new_invite_)); + if (!old_invite_link.is_valid() || !new_invite_link.is_valid()) { + LOG(ERROR) << "Wrong edited invite link: " << old_invite_link << " -> " << new_invite_link; + return nullptr; + } + return make_tl_object( + old_invite_link.get_chat_invite_link_object(td_->contacts_manager_.get()), + new_invite_link.get_chat_invite_link_object(td_->contacts_manager_.get())); + } + case telegram_api::channelAdminLogEventActionExportedInviteRevoke::ID: { + auto action = move_tl_object_as(action_ptr); + DialogInviteLink invite_link(std::move(action->invite_)); + if (!invite_link.is_valid()) { + LOG(ERROR) << "Wrong revoked invite link: " << invite_link; + return nullptr; + } + return make_tl_object( + invite_link.get_chat_invite_link_object(td_->contacts_manager_.get())); + } + case telegram_api::channelAdminLogEventActionExportedInviteDelete::ID: { + auto action = move_tl_object_as(action_ptr); + DialogInviteLink invite_link(std::move(action->invite_)); + if (!invite_link.is_valid()) { + LOG(ERROR) << "Wrong deleted invite link: " << invite_link; + return nullptr; + } + return make_tl_object( + invite_link.get_chat_invite_link_object(td_->contacts_manager_.get())); + } case telegram_api::channelAdminLogEventActionStartGroupCall::ID: { auto action = move_tl_object_as(action_ptr); auto input_group_call_id = InputGroupCallId(action->call_); @@ -31087,10 +31839,27 @@ tl_object_ptr MessagesManager::get_chat_event_action_ob return make_tl_object( td_->contacts_manager_->get_user_id_object(participant.user_id, "LogEventActionParticipantUnmute"), false); } + case telegram_api::channelAdminLogEventActionParticipantVolume::ID: { + auto action = move_tl_object_as(action_ptr); + GroupCallParticipant participant(std::move(action->participant_)); + if (!participant.is_valid()) { + return nullptr; + } + return make_tl_object( + td_->contacts_manager_->get_user_id_object(participant.user_id, "LogEventActionParticipantVolume"), + participant.volume_level); + } case telegram_api::channelAdminLogEventActionToggleGroupCallSetting::ID: { auto action = move_tl_object_as(action_ptr); return make_tl_object(action->join_muted_); } + case telegram_api::channelAdminLogEventActionChangeHistoryTTL::ID: { + auto action = move_tl_object_as(action_ptr); + auto old_value = MessageTtlSetting(clamp(action->prev_value_, 0, 86400 * 366)); + auto new_value = MessageTtlSetting(clamp(action->new_value_, 0, 86400 * 366)); + return make_tl_object(old_value.get_message_ttl_setting_object(), + new_value.get_message_ttl_setting_object()); + } default: UNREACHABLE(); return nullptr; @@ -31732,9 +32501,10 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq on_dialog_updated(dialog_id, "drop have_full_history"); } - if (!d->is_opened && d->messages != nullptr && is_message_unload_enabled()) { + if (!d->is_opened && d->messages != nullptr && is_message_unload_enabled() && !d->has_unload_timeout) { LOG(INFO) << "Schedule unload of " << dialog_id; pending_unload_dialog_timeout_.add_timeout_in(dialog_id.get(), get_unload_dialog_delay()); + d->has_unload_timeout = true; } if (message->ttl > 0 && message->ttl_expires_at != 0) { @@ -31757,6 +32527,19 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq ttl_register_message(dialog_id, message.get(), now); } } + if (message->ttl_period > 0) { + CHECK(dialog_id.get_type() != DialogType::SecretChat); + auto server_time = G()->server_time(); + if (message->date + message->ttl_period <= server_time) { + LOG(INFO) << "Can't add " << message_id << " with expired TTL period to " << dialog_id << " from " << source; + delete_message_from_database(d, message_id, message.get(), true); + debug_add_message_to_dialog_fail_reason_ = "delete expired by TTL period message"; + d->being_added_message_id = MessageId(); + return nullptr; + } else { + ttl_period_register_message(dialog_id, message.get(), server_time); + } + } LOG(INFO) << "Adding not found " << message_id << " to " << dialog_id << " from " << source; if (d->is_empty) { @@ -31831,7 +32614,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } } - LOG(INFO) << "Attach " << message_id << " to the previous " << previous_message_id; + LOG(INFO) << "Attach " << message_id << " to the previous " << previous_message_id << " in " << dialog_id; message->have_previous = true; message->have_next = previous_message->have_next; previous_message->have_next = true; @@ -31852,7 +32635,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } if (next_message != nullptr) { CHECK(!next_message->have_previous); - LOG(INFO) << "Attach " << message_id << " to the next " << next_message->message_id; + LOG(INFO) << "Attach " << message_id << " to the next " << next_message->message_id << " in " << dialog_id; if (from_update && !next_message->message_id.is_yet_unsent()) { LOG(ERROR) << "Attach " << message_id << " from " << source << " to the next " << next_message->message_id << " in " << dialog_id; @@ -31864,7 +32647,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } } if (!is_attached) { - LOG(INFO) << "Can't auto-attach " << message_id; + LOG(INFO) << "Can't auto-attach " << message_id << " in " << dialog_id; message->have_previous = false; message->have_next = false; } @@ -32144,11 +32927,17 @@ MessagesManager::Message *MessagesManager::add_scheduled_message_to_dialog(Dialo return nullptr; } if (message->ttl != 0 || message->ttl_expires_at != 0) { - LOG(ERROR) << "Tried to add " << message_id << " with ttl " << message->ttl << "/" << message->ttl_expires_at + LOG(ERROR) << "Tried to add " << message_id << " with TTL " << message->ttl << "/" << message->ttl_expires_at << " to " << dialog_id << " from " << source; debug_add_message_to_dialog_fail_reason_ = "skip adding secret scheduled message"; return nullptr; } + if (message->ttl_period != 0) { + LOG(ERROR) << "Tried to add " << message_id << " with TTL period " << message->ttl_period << " to " << dialog_id + << " from " << source; + debug_add_message_to_dialog_fail_reason_ = "skip adding auto-deleting scheduled message"; + return nullptr; + } if (td_->auth_manager_->is_bot()) { LOG(ERROR) << "Bot tried to add " << message_id << " to " << dialog_id << " from " << source; debug_add_message_to_dialog_fail_reason_ = "skip adding scheduled message by bot"; @@ -32320,7 +33109,10 @@ void MessagesManager::add_message_to_database(const Dialog *d, const Message *m, int32 ttl_expires_at = 0; if (m->ttl_expires_at != 0) { - ttl_expires_at = static_cast(m->ttl_expires_at - Time::now() + G()->server_time()); + ttl_expires_at = static_cast(m->ttl_expires_at - Time::now() + G()->server_time()) + 1; + } + if (m->ttl_period != 0 && (ttl_expires_at == 0 || m->date + m->ttl_period < ttl_expires_at)) { + ttl_expires_at = m->date + m->ttl_period; } G()->td_db()->get_messages_db_async()->add_message({d->dialog_id, message_id}, unique_message_id, m->sender_user_id, random_id, ttl_expires_at, get_message_index_mask(d->dialog_id, m), @@ -32454,6 +33246,16 @@ void MessagesManager::delete_message_from_database(Dialog *d, MessageId message_ d->deleted_message_ids.insert(message_id); } } + + if (message_id.is_any_server()) { + auto old_message_id = find_old_message_id(d->dialog_id, message_id); + if (old_message_id.is_valid()) { + LOG(WARNING) << "Sent " << FullMessageId{d->dialog_id, message_id} << " was deleted before it was received"; + send_closure_later(actor_id(this), &MessagesManager::delete_messages, d->dialog_id, + vector{old_message_id}, false, Promise()); + delete_update_message_id(d->dialog_id, message_id); + } + } } if (m != nullptr && m->random_id != 0 && (m->is_outgoing || d->dialog_id == get_my_dialog_id())) { @@ -32547,7 +33349,7 @@ void MessagesManager::attach_message_to_previous(Dialog *d, MessageId message_id LOG_CHECK(m->have_previous) << d->dialog_id << " " << message_id << " " << source; --it; LOG_CHECK(*it != nullptr) << d->dialog_id << " " << message_id << " " << source; - LOG(INFO) << "Attach " << message_id << " to the previous " << (*it)->message_id; + LOG(INFO) << "Attach " << message_id << " to the previous " << (*it)->message_id << " in " << d->dialog_id; if ((*it)->have_next) { m->have_next = true; } else { @@ -32565,7 +33367,7 @@ void MessagesManager::attach_message_to_next(Dialog *d, MessageId message_id, co LOG_CHECK(m->have_next) << d->dialog_id << " " << message_id << " " << source; ++it; LOG_CHECK(*it != nullptr) << d->dialog_id << " " << message_id << " " << source; - LOG(INFO) << "Attach " << message_id << " to the next " << (*it)->message_id; + LOG(INFO) << "Attach " << message_id << " to the next " << (*it)->message_id << " in " << d->dialog_id; if ((*it)->have_previous) { m->have_previous = true; } else { @@ -32584,19 +33386,25 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr DialogId dialog_id = d->dialog_id; MessageId message_id = old_message->message_id; + auto old_content_type = old_message->content->get_type(); + auto new_content_type = new_message->content->get_type(); bool is_scheduled = message_id.is_scheduled(); bool need_send_update = false; - bool is_new_available = new_message->content->get_type() != MessageContentType::ChatDeleteHistory; + bool is_new_available = new_content_type != MessageContentType::ChatDeleteHistory; bool replace_legacy = (old_message->legacy_layer != 0 && (new_message->legacy_layer == 0 || old_message->legacy_layer < new_message->legacy_layer)) || - old_message->content->get_type() == MessageContentType::Unsupported; + old_content_type == MessageContentType::Unsupported; bool was_visible_message_reply_info = is_visible_message_reply_info(dialog_id, old_message); if (old_message->date != new_message->date) { if (new_message->date > 0) { - LOG_IF(ERROR, !is_scheduled && !new_message->is_outgoing && dialog_id != get_my_dialog_id()) - << "Date has changed for incoming " << message_id << " in " << dialog_id << " from " << old_message->date - << " to " << new_message->date << ", message content type is " << old_message->content->get_type() << '/' - << new_message->content->get_type(); + if (!(is_scheduled || message_id.is_yet_unsent() || + (message_id.is_server() && message_id.get_server_message_id().get() == 1) || + old_content_type == MessageContentType::ChannelMigrateFrom || + old_content_type == MessageContentType::ChannelCreate)) { + LOG(ERROR) << "Date has changed for " << message_id << " in " << dialog_id << " from " << old_message->date + << " to " << new_message->date << ", message content type is " << old_content_type << '/' + << new_content_type; + } CHECK(old_message->date > 0); LOG(DEBUG) << "Message date has changed from " << old_message->date << " to " << new_message->date; old_message->date = new_message->date; @@ -32606,8 +33414,7 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr need_send_update = true; } else { LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with wrong date " << new_message->date - << ", message content type is " << old_message->content->get_type() << '/' - << new_message->content->get_type(); + << ", message content type is " << old_content_type << '/' << new_content_type; } } if (old_message->date == old_message->edited_schedule_date) { @@ -32625,8 +33432,8 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr need_send_update = true; } } else { - LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " of type " << old_message->content->get_type() - << "/" << new_message->content->get_type() << " with wrong edit date " << new_message->edit_date + LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " of type " << old_content_type << "/" + << new_content_type << " with wrong edit date " << new_message->edit_date << ", old edit date = " << old_message->edit_date; } } @@ -32643,8 +33450,7 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr // there can be race for sent signed posts or changed anonymous flag LOG_IF(ERROR, old_message->sender_user_id != UserId() && new_message->sender_user_id != UserId()) << message_id << " in " << dialog_id << " has changed sender from " << old_message->sender_user_id << " to " - << new_message->sender_user_id << ", message content type is " << old_message->content->get_type() << '/' - << new_message->content->get_type(); + << new_message->sender_user_id << ", message content type is " << old_content_type << '/' << new_content_type; LOG_IF(WARNING, (new_message->sender_user_id.is_valid() || old_message->author_signature.empty()) && !old_message->sender_dialog_id.is_valid() && !new_message->sender_dialog_id.is_valid()) @@ -32658,8 +33464,7 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr // there can be race for changed anonymous flag LOG_IF(ERROR, old_message->sender_dialog_id != DialogId() && new_message->sender_dialog_id != DialogId()) << message_id << " in " << dialog_id << " has changed sender from " << old_message->sender_dialog_id << " to " - << new_message->sender_dialog_id << ", message content type is " << old_message->content->get_type() << '/' - << new_message->content->get_type(); + << new_message->sender_dialog_id << ", message content type is " << old_content_type << '/' << new_content_type; LOG(DEBUG) << "Change message sender"; old_message->sender_dialog_id = new_message->sender_dialog_id; @@ -32670,8 +33475,8 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr if (!replace_legacy) { LOG(ERROR) << message_id << " in " << dialog_id << " has received forward info " << *new_message->forward_info << ", really forwarded from " << old_message->real_forward_from_message_id << " in " - << old_message->real_forward_from_dialog_id << ", message content type is " - << old_message->content->get_type() << '/' << new_message->content->get_type(); + << old_message->real_forward_from_dialog_id << ", message content type is " << old_content_type + << '/' << new_content_type; } old_message->forward_info = std::move(new_message->forward_info); need_send_update = true; @@ -32688,8 +33493,7 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr LOG(ERROR) << message_id << " in " << dialog_id << " has changed forward info from " << *old_message->forward_info << " to " << *new_message->forward_info << ", really forwarded from " << old_message->real_forward_from_message_id << " in " << old_message->real_forward_from_dialog_id - << ", message content type is " << old_message->content->get_type() << '/' - << new_message->content->get_type(); + << ", message content type is " << old_content_type << '/' << new_content_type; } old_message->forward_info = std::move(new_message->forward_info); need_send_update = true; @@ -32698,8 +33502,8 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr LOG(ERROR) << message_id << " in " << dialog_id << " sent by " << old_message->sender_user_id << "/" << old_message->sender_dialog_id << " has lost forward info " << *old_message->forward_info << ", really forwarded from " << old_message->real_forward_from_message_id << " in " - << old_message->real_forward_from_dialog_id << ", message content type is " - << old_message->content->get_type() << '/' << new_message->content->get_type(); + << old_message->real_forward_from_dialog_id << ", message content type is " << old_content_type << '/' + << new_content_type; old_message->forward_info = nullptr; need_send_update = true; } @@ -32714,8 +33518,7 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr if (new_message->notification_id.is_valid()) { LOG(ERROR) << "Notification id for " << message_id << " in " << dialog_id << " has tried to change from " << old_message->notification_id << " to " << new_message->notification_id - << ", message content type is " << old_message->content->get_type() << '/' - << new_message->content->get_type(); + << ", message content type is " << old_content_type << '/' << new_content_type; } } else { CHECK(new_message->notification_id.is_valid()); @@ -32727,6 +33530,17 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr old_message->is_mention_notification_disabled = true; } + if (old_message->ttl_period != new_message->ttl_period) { + if (old_message->ttl_period != 0 || !message_id.is_yet_unsent()) { + LOG(ERROR) << message_id << " in " << dialog_id << " has changed TTL period from " << old_message->ttl_period + << " to " << new_message->ttl_period; + } else { + LOG(DEBUG) << "Change message TTL period"; + old_message->ttl_period = new_message->ttl_period; + need_send_update = true; + } + } + if (old_message->reply_to_message_id != new_message->reply_to_message_id) { // Can't check "&& get_message_force(d, old_message->reply_to_message_id, "update_message") == nullptr", because it // can change message tree and invalidate reference to old_message @@ -32737,8 +33551,7 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr } else if (is_new_available) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed message it is reply to from " << old_message->reply_to_message_id << " to " << new_message->reply_to_message_id - << ", message content type is " << old_message->content->get_type() << '/' - << new_message->content->get_type(); + << ", message content type is " << old_content_type << '/' << new_content_type; } } if (old_message->reply_in_dialog_id != new_message->reply_in_dialog_id) { @@ -32749,8 +33562,7 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr } else if (is_new_available && old_message->reply_in_dialog_id.is_valid()) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed dialog it is reply in from " << old_message->reply_in_dialog_id << " to " << new_message->reply_in_dialog_id - << ", message content type is " << old_message->content->get_type() << '/' - << new_message->content->get_type(); + << ", message content type is " << old_content_type << '/' << new_content_type; } } if (old_message->top_thread_message_id != new_message->top_thread_message_id) { @@ -32762,8 +33574,7 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr } else if (is_new_available) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed message thread from " << old_message->top_thread_message_id << " to " << new_message->top_thread_message_id - << ", message content type is " << old_message->content->get_type() << '/' - << new_message->content->get_type(); + << ", message content type is " << old_content_type << '/' << new_content_type; } } if (old_message->via_bot_user_id != new_message->via_bot_user_id) { @@ -32771,8 +33582,7 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr !replace_legacy) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed bot via it is sent from " << old_message->via_bot_user_id << " to " << new_message->via_bot_user_id - << ", message content type is " << old_message->content->get_type() << '/' - << new_message->content->get_type(); + << ", message content type is " << old_content_type << '/' << new_content_type; } LOG(DEBUG) << "Change message via_bot from " << old_message->via_bot_user_id << " to " << new_message->via_bot_user_id; @@ -32787,25 +33597,24 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr if (old_message->is_outgoing != new_message->is_outgoing && is_new_available) { if (!replace_legacy && !(message_id.is_scheduled() && dialog_id == get_my_dialog_id())) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed is_outgoing from " << old_message->is_outgoing - << " to " << new_message->is_outgoing << ", message content type is " - << old_message->content->get_type() << '/' << new_message->content->get_type(); + << " to " << new_message->is_outgoing << ", message content type is " << old_content_type << '/' + << new_content_type; } old_message->is_outgoing = new_message->is_outgoing; need_send_update = true; } LOG_IF(ERROR, old_message->is_channel_post != new_message->is_channel_post) << message_id << " in " << dialog_id << " has changed is_channel_post from " << old_message->is_channel_post - << " to " << new_message->is_channel_post << ", message content type is " << old_message->content->get_type() - << '/' << new_message->content->get_type(); + << " to " << new_message->is_channel_post << ", message content type is " << old_content_type << '/' + << new_content_type; if (old_message->contains_mention != new_message->contains_mention) { - auto old_type = old_message->content->get_type(); - if (old_message->edit_date == 0 && is_new_available && old_type != MessageContentType::PinMessage && - old_type != MessageContentType::ExpiredPhoto && old_type != MessageContentType::ExpiredVideo && + if (old_message->edit_date == 0 && is_new_available && old_content_type != MessageContentType::PinMessage && + old_content_type != MessageContentType::ExpiredPhoto && old_content_type != MessageContentType::ExpiredVideo && !replace_legacy) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed contains_mention from " << old_message->contains_mention << " to " << new_message->contains_mention - << ", is_outgoing = " << old_message->is_outgoing << ", message content type is " << old_type << '/' - << new_message->content->get_type(); + << ", is_outgoing = " << old_message->is_outgoing << ", message content type is " << old_content_type + << '/' << new_content_type; } // contains_mention flag shouldn't be changed, because the message will not be added to unread mention list // and we are unable to show/hide message notification @@ -32881,14 +33690,12 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr } else { if (old_message->reply_markup == nullptr) { if (new_message->reply_markup != nullptr) { - auto content_type = old_message->content->get_type(); // MessageGame and MessageInvoice reply markup can be generated server side // some forwards retain their reply markup - if (content_type != MessageContentType::Game && content_type != MessageContentType::Invoice && + if (old_content_type != MessageContentType::Game && old_content_type != MessageContentType::Invoice && need_message_changed_warning(old_message) && !replace_legacy) { LOG(ERROR) << message_id << " in " << dialog_id << " has received reply markup " << *new_message->reply_markup - << ", message content type is " << old_message->content->get_type() << '/' - << new_message->content->get_type(); + << ", message content type is " << old_content_type << '/' << new_content_type; } else { LOG(DEBUG) << "Add message reply keyboard"; } @@ -33290,6 +34097,9 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&d, do_set_dialog_folder_id( d.get(), td_->contacts_manager_->get_secret_chat_initial_folder_id(dialog_id.get_secret_chat_id())); } + d->message_ttl_setting = + MessageTtlSetting(td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id())); + d->is_message_ttl_setting_inited = true; break; case DialogType::None: @@ -33377,7 +34187,7 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab if (being_added_dialog_id_ != dialog_id && !td_->auth_manager_->is_bot() && !is_dialog_inited(d) && dialog_type != DialogType::SecretChat && have_input_peer(dialog_id, AccessRights::Read)) { // asynchronously get dialog from the server - send_get_dialog_query(dialog_id, Auto()); + send_get_dialog_query(dialog_id, Auto(), 0, "fix_new_dialog 20"); } if (being_added_dialog_id_ != dialog_id && !d->is_is_blocked_inited && !td_->auth_manager_->is_bot()) { @@ -33393,6 +34203,10 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab // asynchronously get dialog folder id from the server get_dialog_info_full(dialog_id, Auto()); } + if (!d->is_message_ttl_setting_inited && !td_->auth_manager_->is_bot() && order != DEFAULT_ORDER) { + // asynchronously get dialog message ttl setting from the server + get_dialog_info_full(dialog_id, Auto()); + } if (!d->know_action_bar && !td_->auth_manager_->is_bot() && dialog_type != DialogType::SecretChat && dialog_id != get_my_dialog_id() && have_input_peer(dialog_id, AccessRights::Read)) { // asynchronously get action bar from the server @@ -33681,33 +34495,34 @@ void MessagesManager::add_dialog_last_database_message(Dialog *d, unique_ptrlast_database_message_id == message_id) << message_id << " " << d->last_database_message_id << " " << d->debug_set_dialog_last_database_message_id; - if (!have_input_peer(d->dialog_id, AccessRights::Read)) { - // do not add last message to inaccessible dialog + bool need_update_dialog_pos = false; + const Message *m = nullptr; + if (have_input_peer(d->dialog_id, AccessRights::Read)) { + bool need_update = false; + last_database_message->have_previous = false; + last_database_message->have_next = false; + last_database_message->from_database = true; + m = add_message_to_dialog(d, std::move(last_database_message), false, &need_update, &need_update_dialog_pos, + "add_dialog_last_database_message 1"); + if (need_update_dialog_pos) { + LOG(ERROR) << "Need to update pos in " << d->dialog_id; + } + } + if (m != nullptr) { + set_dialog_last_message_id(d, m->message_id, "add_dialog_last_database_message 2"); + send_update_chat_last_message(d, "add_dialog_last_database_message 3"); + } else { if (d->pending_last_message_date != 0) { d->pending_last_message_date = 0; d->pending_last_message_id = MessageId(); - update_dialog_pos(d, "add_dialog_last_database_message 1"); + need_update_dialog_pos = true; } - return; - } + on_dialog_updated(d->dialog_id, "add_dialog_last_database_message 4"); // resave without last database message - bool need_update = false; - bool need_update_dialog_pos = false; - last_database_message->have_previous = false; - last_database_message->have_next = false; - last_database_message->from_database = true; - Message *m = add_message_to_dialog(d, std::move(last_database_message), false, &need_update, &need_update_dialog_pos, - "add_dialog_last_database_message 2"); - if (need_update_dialog_pos) { - LOG(ERROR) << "Need to update pos in " << d->dialog_id; - } - if (m != nullptr) { - set_dialog_last_message_id(d, message_id, "add_dialog_last_database_message 3"); - send_update_chat_last_message(d, "add_dialog_last_database_message 4"); - } else if (d->pending_last_message_date != 0) { - d->pending_last_message_date = 0; - d->pending_last_message_id = MessageId(); - need_update_dialog_pos = true; + if (!td_->auth_manager_->is_bot() && d->dialog_id != being_added_dialog_id_ && + have_input_peer(d->dialog_id, AccessRights::Read) && (d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) { + get_history_from_the_end(d->dialog_id, true, false, Auto()); + } } if (need_update_dialog_pos) { @@ -34335,7 +35150,7 @@ unique_ptr MessagesManager::parse_dialog(DialogId dialo have_dialog_info_force(dialog_id); if (have_input_peer(dialog_id, AccessRights::Read)) { if (dialog_id.get_type() != DialogType::SecretChat) { - send_get_dialog_query(dialog_id, Auto()); + send_get_dialog_query(dialog_id, Auto(), 0, "parse_dialog"); } } else { LOG(WARNING) << "Have no info about " << dialog_id << " to repair it"; @@ -34692,7 +35507,7 @@ string MessagesManager::get_channel_pts_key(DialogId dialog_id) { } int32 MessagesManager::load_channel_pts(DialogId dialog_id) const { - if (G()->ignore_backgrond_updates()) { + if (G()->ignore_background_updates()) { G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(dialog_id)); // just in case return 0; } @@ -34741,7 +35556,7 @@ void MessagesManager::set_channel_pts(Dialog *d, int32 new_pts, const char *sour repair_channel_server_unread_count(d); } } - if (!G()->ignore_backgrond_updates()) { + if (!G()->ignore_background_updates()) { G()->td_db()->get_binlog_pmc()->set(get_channel_pts_key(d->dialog_id), to_string(new_pts)); } } else if (new_pts < d->pts) { @@ -34751,6 +35566,7 @@ void MessagesManager::set_channel_pts(Dialog *d, int32 new_pts, const char *sour bool MessagesManager::need_channel_difference_to_add_message(DialogId dialog_id, const tl_object_ptr &message_ptr) { + // keep consistent with add_message_to_dialog if (dialog_id.get_type() != DialogType::Channel || !have_input_peer(dialog_id, AccessRights::Read) || dialog_id == debug_channel_difference_dialog_) { return false; @@ -34849,7 +35665,7 @@ void MessagesManager::get_channel_difference(DialogId dialog_id, int32 pts, bool return; } - if (force && get_channel_difference_to_log_event_id_.count(dialog_id) == 0 && !G()->ignore_backgrond_updates()) { + if (force && get_channel_difference_to_log_event_id_.count(dialog_id) == 0 && !G()->ignore_background_updates()) { auto channel_id = dialog_id.get_channel_id(); CHECK(input_channel->get_id() == telegram_api::inputChannel::ID); auto access_hash = static_cast(*input_channel).access_hash_; @@ -34998,7 +35814,7 @@ void MessagesManager::on_get_channel_dialog(DialogId dialog_id, MessageId last_m // as results of getChatHistory and (if implemented continuous ranges support for searching shared media) // searchChatMessages. The messages should still be lazily checked using getHistory, but they are still available // offline. It is the best way for gaps support, but it is pretty hard to implement correctly. - // It should be also noted that some messages like live location messages shouldn't be deleted. + // It should be also noted that some messages like outgoing live location messages shouldn't be deleted. if (last_message_id > d->last_new_message_id) { // TODO properly support last_message_id <= d->last_new_message_id @@ -35188,7 +36004,7 @@ void MessagesManager::on_get_channel_difference( } auto new_pts = dialog->pts_; - if (request_pts + request_limit > new_pts) { + if (request_pts > new_pts - request_limit) { LOG(ERROR) << "Receive channelDifferenceTooLong as result of getChannelDifference with pts = " << request_pts << " and limit = " << request_limit << " in " << dialog_id << ", but pts has changed from " << d->pts << " to " << new_pts << ". Difference: " << oneline(to_string(difference)); @@ -35283,7 +36099,7 @@ void MessagesManager::after_get_channel_difference(DialogId dialog_id, bool succ promise.set_value(Unit()); } if (d->postponed_channel_updates.size() != old_size || running_get_channel_difference(dialog_id)) { - if (success && update_pts < d->pts + 10000 && update_pts_count == 1) { + if (success && update_pts - 10000 < d->pts && update_pts_count == 1) { // if getChannelDifference was successful and update pts is near channel pts, // we hope that the update eventually can be applied LOG(INFO) << "Can't apply postponed channel updates"; @@ -35569,7 +36385,9 @@ MessagesManager::Message *MessagesManager::continue_send_message(DialogId dialog add_message_to_dialog(d, std::move(m), true, &need_update, &need_update_dialog_pos, "continue_send_message"); CHECK(result_message != nullptr); - send_update_chat_has_scheduled_messages(d, false); + if (result_message->message_id.is_scheduled()) { + send_update_chat_has_scheduled_messages(d, false); + } send_update_new_message(d, result_message); if (need_update_dialog_pos) { @@ -35885,6 +36703,13 @@ void MessagesManager::on_binlog_events(vector &&events) { log_event.revoke_, true, event.id_, Auto()); break; } + case LogEvent::HandlerType::DeleteAllCallMessagesFromServer: { + DeleteAllCallMessagesFromServerLogEvent log_event; + log_event_parse(log_event, event.data_).ensure(); + + delete_all_call_messages_from_server(log_event.revoke_, event.id_, Auto()); + break; + } case LogEvent::HandlerType::BlockMessageSenderFromRepliesOnServer: { BlockMessageSenderFromRepliesOnServerLogEvent log_event; log_event_parse(log_event, event.data_).ensure(); @@ -36234,7 +37059,7 @@ void MessagesManager::on_binlog_events(vector &&events) { break; } - send_get_dialog_query(dialog_id, Promise(), event.id_); + send_get_dialog_query(dialog_id, Promise(), event.id_, "GetDialogFromServerLogEvent"); break; } case LogEvent::HandlerType::UnpinAllDialogMessagesOnServer: { @@ -36250,7 +37075,7 @@ void MessagesManager::on_binlog_events(vector &&events) { break; } case LogEvent::HandlerType::GetChannelDifference: { - if (G()->ignore_backgrond_updates()) { + if (G()->ignore_background_updates()) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 4ef6daf95..a76aa53ce 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -10,7 +10,6 @@ #include "td/telegram/ChannelId.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogAction.h" -#include "td/telegram/DialogAdministrator.h" #include "td/telegram/DialogDate.h" #include "td/telegram/DialogDb.h" #include "td/telegram/DialogFilterId.h" @@ -33,6 +32,7 @@ #include "td/telegram/MessageReplyInfo.h" #include "td/telegram/MessagesDb.h" #include "td/telegram/MessageSearchFilter.h" +#include "td/telegram/MessageTtlSetting.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/Notification.h" #include "td/telegram/NotificationGroupId.h" @@ -41,6 +41,7 @@ #include "td/telegram/NotificationId.h" #include "td/telegram/NotificationSettings.h" #include "td/telegram/ReplyMarkup.h" +#include "td/telegram/ReportReason.h" #include "td/telegram/RestrictionReason.h" #include "td/telegram/ScheduledServerMessageId.h" #include "td/telegram/SecretChatId.h" @@ -118,6 +119,7 @@ class MessagesManager : public Actor { static constexpr int32 MESSAGE_FLAG_IS_RESTRICTED = 1 << 22; static constexpr int32 MESSAGE_FLAG_HAS_REPLY_INFO = 1 << 23; static constexpr int32 MESSAGE_FLAG_IS_PINNED = 1 << 24; + static constexpr int32 MESSAGE_FLAG_HAS_TTL_PERIOD = 1 << 25; static constexpr int32 SEND_MESSAGE_FLAG_IS_REPLY = 1 << 0; static constexpr int32 SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW = 1 << 1; @@ -238,7 +240,8 @@ class MessagesManager : public Actor { void delete_secret_messages(SecretChatId secret_chat_id, std::vector random_ids, Promise<> promise); - void delete_secret_chat_history(SecretChatId secret_chat_id, MessageId last_message_id, Promise<> promise); + void delete_secret_chat_history(SecretChatId secret_chat_id, bool remove_from_dialog_list, MessageId last_message_id, + Promise<> promise); void read_secret_chat_outbox(SecretChatId secret_chat_id, int32 up_to_date, int32 read_date); @@ -291,6 +294,8 @@ class MessagesManager : public Actor { void on_update_dialog_group_call_id(DialogId dialog_id, InputGroupCallId input_group_call_id); + void on_update_dialog_message_ttl_setting(DialogId dialog_id, MessageTtlSetting message_ttl_setting); + void on_update_dialog_filters(); void on_update_service_notification(tl_object_ptr &&update, @@ -342,9 +347,11 @@ class MessagesManager : public Actor { void delete_dialog_history(DialogId dialog_id, bool remove_from_dialog_list, bool revoke, Promise &&promise); + void delete_all_call_messages(bool revoke, Promise &&promise); + void delete_dialog_messages_from_user(DialogId dialog_id, UserId user_id, Promise &&promise); - void delete_dialog(DialogId dialog_id); + void on_dialog_deleted(DialogId dialog_id, Promise &&promise); void on_update_dialog_group_call_rights(DialogId dialog_id); @@ -386,7 +393,7 @@ class MessagesManager : public Actor { Result> resend_messages(DialogId dialog_id, vector message_ids) TD_WARN_UNUSED_RESULT; - Result send_dialog_set_ttl_message(DialogId dialog_id, int32 ttl); + void set_dialog_message_ttl_setting(DialogId dialog_id, int32 ttl, Promise &&promise); Status send_screenshot_taken_notification_message(DialogId dialog_id); @@ -395,6 +402,17 @@ class MessagesManager : public Actor { tl_object_ptr &&input_message_content) TD_WARN_UNUSED_RESULT; + void get_message_file_type(const string &message_file_head, + Promise> &&promise); + + void get_message_import_confirmation_text(DialogId dialog_id, Promise &&promise); + + void import_messages(DialogId dialog_id, const td_api::object_ptr &message_file, + const vector> &attached_files, Promise &&promise); + + void start_import_messages(DialogId dialog_id, int64 import_id, vector &&attached_file_ids, + Promise &&promise); + void edit_message_text(FullMessageId full_message_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_message_content, Promise &&promise); @@ -470,28 +488,6 @@ class MessagesManager : public Actor { void unpin_all_dialog_messages(DialogId dialog_id, Promise &&promise); - void add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit, Promise &&promise); - - void add_dialog_participants(DialogId dialog_id, const vector &user_ids, Promise &&promise); - - void set_dialog_participant_status(DialogId dialog_id, UserId user_id, - const tl_object_ptr &chat_member_status, - Promise &&promise); - - DialogParticipant get_dialog_participant(DialogId dialog_id, UserId user_id, int64 &random_id, bool force, - Promise &&promise); - - std::pair> search_dialog_participants(DialogId dialog_id, const string &query, - int32 limit, DialogParticipantsFilter filter, - int64 &random_id, bool without_bot_info, - bool force, Promise &&promise); - - vector get_dialog_administrators(DialogId dialog_id, int left_tries, Promise &&promise); - - void export_dialog_invite_link(DialogId dialog_id, Promise &&promise); - - string get_dialog_invite_link(DialogId dialog_id); - void get_dialog_info_full(DialogId dialog_id, Promise &&promise); int64 get_dialog_event_log(DialogId dialog_id, const string &query, int64 from_event_id, int32 limit, @@ -632,7 +628,8 @@ class MessagesManager : public Actor { Promise &&promise); DialogId create_new_channel_chat(const string &title, bool is_megagroup, const string &description, - const DialogLocation &location, int64 &random_id, Promise &&promise); + const DialogLocation &location, bool for_import, int64 &random_id, + Promise &&promise); void create_new_secret_chat(UserId user_id, Promise &&promise); @@ -754,7 +751,7 @@ class MessagesManager : public Actor { bool is_update_about_username_change_received(DialogId dialog_id) const; - void on_dialog_bots_updated(DialogId dialog_id, vector bot_user_ids); + void on_dialog_bots_updated(DialogId dialog_id, vector bot_user_ids, bool from_database); void on_dialog_photo_updated(DialogId dialog_id); void on_dialog_title_updated(DialogId dialog_id); @@ -785,8 +782,10 @@ class MessagesManager : public Actor { void reget_dialog_action_bar(DialogId dialog_id, const char *source); - void report_dialog(DialogId dialog_id, const tl_object_ptr &reason, - const vector &message_ids, Promise &&promise); + void report_dialog(DialogId dialog_id, const vector &message_ids, ReportReason &&reason, + Promise &&promise); + + void report_dialog_photo(DialogId dialog_id, FileId file_id, ReportReason &&reason, Promise &&promise); void on_get_peer_settings(DialogId dialog_id, tl_object_ptr &&peer_settings, bool ignore_privacy_exception = false); @@ -813,8 +812,8 @@ class MessagesManager : public Actor { void check_send_message_result(int64 random_id, DialogId dialog_id, const telegram_api::Updates *updates_ptr, const char *source); - FullMessageId on_send_message_success(int64 random_id, MessageId new_message_id, int32 date, FileId new_file_id, - const char *source); + FullMessageId on_send_message_success(int64 random_id, MessageId new_message_id, int32 date, int32 ttl_period, + FileId new_file_id, const char *source); void on_send_message_file_part_missing(int64 random_id, int bad_part); @@ -938,6 +937,7 @@ class MessagesManager : public Actor { UserId sender_user_id; DialogId sender_dialog_id; int32 date = 0; + int32 ttl_period = 0; int32 ttl = 0; int64 random_id = 0; tl_object_ptr forward_header; @@ -967,12 +967,13 @@ class MessagesManager : public Actor { DialogId from_dialog_id; MessageId from_message_id; string psa_type; + bool is_imported = false; MessageForwardInfo() = default; MessageForwardInfo(UserId sender_user_id, int32 date, DialogId sender_dialog_id, MessageId message_id, string author_signature, string sender_name, DialogId from_dialog_id, MessageId from_message_id, - string psa_type) + string psa_type, bool is_imported) : sender_user_id(sender_user_id) , date(date) , sender_dialog_id(sender_dialog_id) @@ -981,14 +982,15 @@ class MessagesManager : public Actor { , sender_name(std::move(sender_name)) , from_dialog_id(from_dialog_id) , from_message_id(from_message_id) - , psa_type(psa_type) { + , psa_type(psa_type) + , is_imported(is_imported) { } bool operator==(const MessageForwardInfo &rhs) const { return sender_user_id == rhs.sender_user_id && date == rhs.date && sender_dialog_id == rhs.sender_dialog_id && message_id == rhs.message_id && author_signature == rhs.author_signature && sender_name == rhs.sender_name && from_dialog_id == rhs.from_dialog_id && - from_message_id == rhs.from_message_id && psa_type == rhs.psa_type; + from_message_id == rhs.from_message_id && psa_type == rhs.psa_type && is_imported == rhs.is_imported; } bool operator!=(const MessageForwardInfo &rhs) const { @@ -996,11 +998,13 @@ class MessagesManager : public Actor { } friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageForwardInfo &forward_info) { - return string_builder << "MessageForwardInfo[sender " << forward_info.sender_user_id << "(" - << forward_info.author_signature << "/" << forward_info.sender_name << "), psa_type " - << forward_info.psa_type << ", source " << forward_info.sender_dialog_id << ", source " - << forward_info.message_id << ", from " << forward_info.from_dialog_id << ", from " - << forward_info.from_message_id << " at " << forward_info.date << "]"; + return string_builder << "MessageForwardInfo[" << (forward_info.is_imported ? "imported " : "") << "sender " + << forward_info.sender_user_id << "(" << forward_info.author_signature << "/" + << forward_info.sender_name << "), psa_type " << forward_info.psa_type << ", source " + << forward_info.sender_dialog_id << ", source " << forward_info.message_id << ", from " + << forward_info.from_dialog_id << ", from " << forward_info.from_message_id << " at " + << forward_info.date << " " + << "]"; } }; @@ -1078,8 +1082,9 @@ class MessagesManager : public Actor { string send_error_message; double try_resend_at = 0; - int32 ttl = 0; - double ttl_expires_at = 0; + int32 ttl_period = 0; // counted from message send date + int32 ttl = 0; // counted from message content view date + double ttl_expires_at = 0; // only for ttl int64 media_album_id = 0; @@ -1152,6 +1157,7 @@ class MessagesManager : public Actor { MessageId last_pinned_message_id; MessageId reply_markup_message_id; DialogNotificationSettings notification_settings; + MessageTtlSetting message_ttl_setting; unique_ptr draft_message; LogEventIdWithGeneration save_draft_message_log_event_id; LogEventIdWithGeneration save_notification_settings_log_event_id; @@ -1206,6 +1212,7 @@ class MessagesManager : public Actor { bool can_report_location = false; bool can_unarchive = false; bool hide_distance = false; + bool can_invite_members = false; bool is_opened = false; @@ -1232,10 +1239,12 @@ class MessagesManager : public Actor { bool had_last_yet_unsent_message = false; // whether the dialog was stored to database without last message bool has_active_group_call = false; bool is_group_call_empty = false; + bool is_message_ttl_setting_inited = false; bool increment_view_counter = false; bool is_update_new_chat_sent = false; + bool has_unload_timeout = false; int32 pts = 0; // for channels only std::multimap postponed_channel_updates; // for channels only @@ -1582,6 +1591,7 @@ class MessagesManager : public Actor { DialogId dialog_id; vector random_ids; MessageId last_message_id; + bool remove_from_dialog_list = false; Promise<> success_promise; }; @@ -1601,6 +1611,7 @@ class MessagesManager : public Actor { class ChangeDialogReportSpamStateOnServerLogEvent; class DeleteAllChannelMessagesFromUserOnServerLogEvent; class DeleteDialogHistoryFromServerLogEvent; + class DeleteAllCallMessagesFromServerLogEvent; class DeleteMessageLogEvent; class DeleteMessagesFromServerLogEvent; class DeleteScheduledMessagesFromServerLogEvent; @@ -1709,13 +1720,17 @@ class MessagesManager : public Actor { DialogId get_my_dialog_id() const; + void on_resolve_secret_chat_message_via_bot_username(const string &via_bot_username, MessageInfo *message_info_ptr, + Promise &&promise); + void add_secret_message(unique_ptr pending_secret_message, Promise lock_promise = Auto()); void finish_add_secret_message(unique_ptr pending_secret_message); void finish_delete_secret_messages(DialogId dialog_id, std::vector random_ids, Promise<> promise); - void finish_delete_secret_chat_history(DialogId dialog_id, MessageId last_message_id, Promise<> promise); + void finish_delete_secret_chat_history(DialogId dialog_id, bool remove_from_dialog_list, MessageId last_message_id, + Promise<> promise); MessageInfo parse_telegram_api_message(tl_object_ptr message_ptr, bool is_scheduled, const char *source) const; @@ -1724,6 +1739,8 @@ class MessagesManager : public Actor { MessageId find_old_message_id(DialogId dialog_id, MessageId message_id) const; + void delete_update_message_id(DialogId dialog_id, MessageId message_id); + FullMessageId on_get_message(MessageInfo &&message_info, bool from_update, bool is_channel_message, bool have_previous, bool have_next, const char *source); @@ -1787,8 +1804,8 @@ class MessagesManager : public Actor { void delete_messages_from_updates(const vector &message_ids); - void delete_dialog_messages_from_updates(DialogId dialog_id, const vector &message_ids, - bool skip_update_for_not_found_messages); + void delete_dialog_messages(DialogId dialog_id, const vector &message_ids, bool from_updates, + bool skip_update_for_not_found_messages); void update_dialog_pinned_messages_from_updates(DialogId dialog_id, const vector &message_ids, bool is_pin); @@ -1895,6 +1912,8 @@ class MessagesManager : public Actor { void delete_dialog_history_from_server(DialogId dialog_id, MessageId max_message_id, bool remove_from_dialog_list, bool revoke, bool allow_error, uint64 log_event_id, Promise &&promise); + void delete_all_call_messages_from_server(bool revoke, uint64 log_event_id, Promise &&promise); + void block_message_sender_from_replies_on_server(MessageId message_id, bool delete_message, bool delete_all_messages, bool report_spam, uint64 log_event_id, Promise &&promise); @@ -2213,6 +2232,8 @@ class MessagesManager : public Actor { void send_update_chat_voice_chat(const Dialog *d); + void send_update_chat_message_ttl_setting(const Dialog *d); + void send_update_chat_has_scheduled_messages(Dialog *d, bool from_deletion); void send_update_user_chat_action(DialogId dialog_id, MessageId top_thread_message_id, UserId user_id, @@ -2408,7 +2429,7 @@ class MessagesManager : public Actor { void on_get_dialogs_from_database(FolderId folder_id, int32 limit, DialogDbGetDialogsResult &&dialogs, Promise &&promise); - void send_get_dialog_query(DialogId dialog_id, Promise &&promise, uint64 log_event_id = 0); + void send_get_dialog_query(DialogId dialog_id, Promise &&promise, uint64 log_event_id, const char *source); void send_search_public_dialogs_query(const string &query, Promise &&promise); @@ -2516,10 +2537,6 @@ class MessagesManager : public Actor { DialogFolder *get_dialog_folder(FolderId folder_id); const DialogFolder *get_dialog_folder(FolderId folder_id) const; - std::pair> search_private_chat_participants(UserId my_user_id, UserId peer_user_id, - const string &query, int32 limit, - DialogParticipantsFilter filter) const; - static unique_ptr *treap_find_message(unique_ptr *v, MessageId message_id); static const unique_ptr *treap_find_message(const unique_ptr *v, MessageId message_id); @@ -2593,7 +2610,9 @@ class MessagesManager : public Actor { void ttl_on_view(const Dialog *d, Message *m, double view_date, double now); bool ttl_on_open(Dialog *d, Message *m, double now, bool is_local_read); void ttl_register_message(DialogId dialog_id, const Message *m, double now); - void ttl_unregister_message(DialogId dialog_id, const Message *m, double now, const char *source); + void ttl_unregister_message(DialogId dialog_id, const Message *m, const char *source); + void ttl_period_register_message(DialogId dialog_id, const Message *m, double server_time); + void ttl_period_unregister_message(DialogId dialog_id, const Message *m); void ttl_loop(double now); void ttl_update_timeout(double now); @@ -2817,6 +2836,22 @@ class MessagesManager : public Actor { tl_object_ptr &&input_chat_photo, Promise &&promise); + void upload_imported_messages(DialogId dialog_id, FileId file_id, vector attached_file_ids, bool is_reupload, + Promise &&promise, vector bad_parts = {}); + + void on_upload_imported_messages(FileId file_id, tl_object_ptr input_file); + void on_upload_imported_messages_error(FileId file_id, Status status); + + void upload_imported_message_attachment(DialogId dialog_id, int64 import_id, FileId file_id, bool is_reupload, + Promise &&promise, vector bad_parts = {}); + + void on_upload_imported_message_attachment(FileId file_id, tl_object_ptr input_file); + void on_upload_imported_message_attachment_error(FileId file_id, Status status); + + void on_imported_message_attachments_uploaded(int64 random_id, Result &&result); + + Status can_import_messages(DialogId dialog_id); + void add_sponsored_dialog(const Dialog *d, DialogSource source); void save_sponsored_dialog(); @@ -2892,6 +2927,8 @@ class MessagesManager : public Actor { uint64 save_delete_dialog_history_from_server_log_event(DialogId dialog_id, MessageId max_message_id, bool remove_from_dialog_list, bool revoke); + uint64 save_delete_all_call_messages_from_server_log_event(bool revoke); + uint64 save_block_message_sender_from_replies_on_server_log_event(MessageId message_id, bool delete_message, bool delete_all_messages, bool report_spam); @@ -2947,10 +2984,14 @@ class MessagesManager : public Actor { class UploadMediaCallback; class UploadThumbnailCallback; class UploadDialogPhotoCallback; + class UploadImportedMessagesCallback; + class UploadImportedMessageAttachmentCallback; std::shared_ptr upload_media_callback_; std::shared_ptr upload_thumbnail_callback_; std::shared_ptr upload_dialog_photo_callback_; + std::shared_ptr upload_imported_messages_callback_; + std::shared_ptr upload_imported_message_attachment_callback_; double last_channel_pts_jump_warning_time_ = 0; @@ -2973,10 +3014,12 @@ class MessagesManager : public Actor { // TTL class TtlNode : private HeapNode { public: - TtlNode(DialogId dialog_id, MessageId message_id) : full_message_id(dialog_id, message_id) { + TtlNode(DialogId dialog_id, MessageId message_id, bool by_ttl_period) + : full_message_id_(dialog_id, message_id), by_ttl_period_(by_ttl_period) { } - FullMessageId full_message_id; + FullMessageId full_message_id_; + bool by_ttl_period_; HeapNode *as_heap_node() const { return const_cast(static_cast(this)); @@ -2986,12 +3029,12 @@ class MessagesManager : public Actor { } bool operator==(const TtlNode &other) const { - return full_message_id == other.full_message_id; + return full_message_id_ == other.full_message_id_; } }; struct TtlNodeHash { std::size_t operator()(const TtlNode &ttl_node) const { - return FullMessageIdHash()(ttl_node.full_message_id); + return FullMessageIdHash()(ttl_node.full_message_id_) * 2 + static_cast(ttl_node.by_ttl_period_); } }; std::unordered_set ttl_nodes_; @@ -3031,7 +3074,45 @@ class MessagesManager : public Actor { , promise(std::move(promise)) { } }; - std::unordered_map being_uploaded_dialog_photos_; // file_id -> ... + std::unordered_map being_uploaded_dialog_photos_; + + struct UploadedImportedMessagesInfo { + DialogId dialog_id; + vector attached_file_ids; + bool is_reupload; + Promise promise; + + UploadedImportedMessagesInfo(DialogId dialog_id, vector &&attached_file_ids, bool is_reupload, + Promise &&promise) + : dialog_id(dialog_id) + , attached_file_ids(std::move(attached_file_ids)) + , is_reupload(is_reupload) + , promise(std::move(promise)) { + } + }; + std::unordered_map, FileIdHash> being_uploaded_imported_messages_; + + struct UploadedImportedMessageAttachmentInfo { + DialogId dialog_id; + int64 import_id; + bool is_reupload; + Promise promise; + + UploadedImportedMessageAttachmentInfo(DialogId dialog_id, int64 import_id, bool is_reupload, + Promise &&promise) + : dialog_id(dialog_id), import_id(import_id), is_reupload(is_reupload), promise(std::move(promise)) { + } + }; + std::unordered_map, FileIdHash> + being_uploaded_imported_message_attachments_; + + struct PendingMessageImport { + MultiPromiseActor upload_files_multipromise{"UploadAttachedFilesMultiPromiseActor"}; + DialogId dialog_id; + int64 import_id = 0; + Promise promise; + }; + std::unordered_map> pending_message_imports_; struct PendingMessageGroupSend { DialogId dialog_id; diff --git a/td/telegram/NotificationManager.cpp b/td/telegram/NotificationManager.cpp index 9d7ca5a62..a08d72b95 100644 --- a/td/telegram/NotificationManager.cpp +++ b/td/telegram/NotificationManager.cpp @@ -3273,9 +3273,9 @@ Status NotificationManager::process_push_notification_payload(string payload, bo auto user = telegram_api::make_object( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, sender_user_id.get(), - sender_access_hash, user_name, string(), string(), string(), std::move(sender_photo), nullptr, 0, Auto(), - string(), string()); + false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + sender_user_id.get(), sender_access_hash, user_name, string(), string(), string(), std::move(sender_photo), + nullptr, 0, Auto(), string(), string()); td_->contacts_manager_->on_get_user(std::move(user), "process_push_notification_payload"); } @@ -3610,8 +3610,9 @@ void NotificationManager::add_message_push_notification(DialogId dialog_id, Mess auto user = telegram_api::make_object( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, sender_user_id.get(), 0, user_name, - string(), string(), string(), nullptr, nullptr, 0, Auto(), string(), string()); + false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + sender_user_id.get(), 0, user_name, string(), string(), string(), nullptr, nullptr, 0, Auto(), string(), + string()); td_->contacts_manager_->on_get_user(std::move(user), "add_message_push_notification"); } diff --git a/td/telegram/Payments.cpp b/td/telegram/Payments.cpp index aa497a0a1..5896d1c5b 100644 --- a/td/telegram/Payments.cpp +++ b/td/telegram/Payments.cpp @@ -859,10 +859,10 @@ void send_payment_form(ServerMessageId server_message_id, const string &order_in flags, false /*ignored*/, make_tl_object(credentials_new->data_)); break; } - case td_api::inputCredentialsAndroidPay::ID: { - auto credentials_android_pay = static_cast(credentials.get()); - input_credentials = make_tl_object( - make_tl_object(credentials_android_pay->data_), string()); + case td_api::inputCredentialsGooglePay::ID: { + auto credentials_google_pay = static_cast(credentials.get()); + input_credentials = make_tl_object( + make_tl_object(credentials_google_pay->data_)); break; } case td_api::inputCredentialsApplePay::ID: { diff --git a/td/telegram/Photo.cpp b/td/telegram/Photo.cpp index 35bf7ead9..8bbc7cb4a 100644 --- a/td/telegram/Photo.cpp +++ b/td/telegram/Photo.cpp @@ -30,18 +30,18 @@ namespace td { -static uint16 get_dimension(int32 size) { +static uint16 get_dimension(int32 size, const char *source) { if (size < 0 || size > 65535) { - LOG(ERROR) << "Wrong image dimension = " << size; + LOG(ERROR) << "Wrong image dimension = " << size << " from " << source; return 0; } return narrow_cast(size); } -Dimensions get_dimensions(int32 width, int32 height) { +Dimensions get_dimensions(int32 width, int32 height, const char *source) { Dimensions result; - result.width = get_dimension(width); - result.height = get_dimension(height); + result.width = get_dimension(width, source); + result.height = get_dimension(height, source); if (result.width == 0 || result.height == 0) { result.width = 0; result.height = 0; @@ -339,7 +339,7 @@ PhotoSize get_secret_thumbnail_photo_size(FileManager *file_manager, BufferSlice } PhotoSize res; res.type = 't'; - res.dimensions = get_dimensions(width, height); + res.dimensions = get_dimensions(width, height, "get_secret_thumbnail_photo_size"); res.size = narrow_cast(bytes.size()); // generate some random remote location to save @@ -360,7 +360,7 @@ PhotoSize get_secret_thumbnail_photo_size(FileManager *file_manager, BufferSlice Variant get_photo_size(FileManager *file_manager, PhotoSizeSource source, int64 id, int64 access_hash, std::string file_reference, DcId dc_id, DialogId owner_dialog_id, tl_object_ptr &&size_ptr, - PhotoFormat format, bool expect_jpeg_minithumbnail) { + PhotoFormat format) { CHECK(size_ptr != nullptr); tl_object_ptr location; @@ -375,7 +375,7 @@ Variant get_photo_size(FileManager *file_manager, PhotoSizeSo type = std::move(size->type_); location = std::move(size->location_); - res.dimensions = get_dimensions(size->w_, size->h_); + res.dimensions = get_dimensions(size->w_, size->h_, "photoSize"); res.size = size->size_; break; @@ -386,7 +386,7 @@ Variant get_photo_size(FileManager *file_manager, PhotoSizeSo type = std::move(size->type_); location = std::move(size->location_); CHECK(size->bytes_.size() <= static_cast(std::numeric_limits::max())); - res.dimensions = get_dimensions(size->w_, size->h_); + res.dimensions = get_dimensions(size->w_, size->h_, "photoCachedSize"); res.size = static_cast(size->bytes_.size()); content = std::move(size->bytes_); @@ -395,11 +395,11 @@ Variant get_photo_size(FileManager *file_manager, PhotoSizeSo } case telegram_api::photoStrippedSize::ID: { auto size = move_tl_object_as(size_ptr); - if (!expect_jpeg_minithumbnail) { + if (format != PhotoFormat::Jpeg) { if (G()->shared_config().get_option_boolean("disable_minithumbnails")) { LOG(DEBUG) << "Receive unexpected JPEG minithumbnail"; } else { - LOG(ERROR) << "Receive unexpected JPEG minithumbnail"; + LOG(ERROR) << "Receive unexpected JPEG minithumbnail in photo of format " << format; } return std::move(res); } @@ -416,7 +416,7 @@ Variant get_photo_size(FileManager *file_manager, PhotoSizeSo type = std::move(size->type_); location = std::move(size->location_); - res.dimensions = get_dimensions(size->w_, size->h_); + res.dimensions = get_dimensions(size->w_, size->h_, "photoSizeProgressive"); res.size = size->sizes_.back(); size->sizes_.pop_back(); res.progressive_sizes = std::move(size->sizes_); @@ -425,8 +425,8 @@ Variant get_photo_size(FileManager *file_manager, PhotoSizeSo } case telegram_api::photoPathSize::ID: { auto size = move_tl_object_as(size_ptr); - if (expect_jpeg_minithumbnail) { - LOG(ERROR) << "Receive unexpected SVG minithumbnail"; + if (format != PhotoFormat::Tgs && format != PhotoFormat::Webp) { + LOG(ERROR) << "Receive unexpected SVG minithumbnail in photo of format " << format; return std::move(res); } return size->bytes_.as_slice().str(); @@ -468,7 +468,7 @@ AnimationSize get_animation_size(FileManager *file_manager, PhotoSizeSource sour LOG(ERROR) << "Wrong videoSize \"" << size->type_ << "\" in " << to_string(size); } res.type = static_cast(size->type_[0]); - res.dimensions = get_dimensions(size->w_, size->h_); + res.dimensions = get_dimensions(size->w_, size->h_, "get_animation_size"); res.size = size->size_; if ((size->flags_ & telegram_api::videoSize::VIDEO_START_TS_MASK) != 0) { res.main_frame_timestamp = size->video_start_ts_; @@ -542,7 +542,7 @@ PhotoSize get_web_document_photo_size(FileManager *file_manager, FileType file_t switch (attribute->get_id()) { case telegram_api::documentAttributeImageSize::ID: { auto image_size = move_tl_object_as(attribute); - dimensions = get_dimensions(image_size->w_, image_size->h_); + dimensions = get_dimensions(image_size->w_, image_size->h_, "web documentAttributeImageSize"); break; } case telegram_api::documentAttributeAnimated::ID: @@ -692,7 +692,7 @@ Photo get_encrypted_file_photo(FileManager *file_manager, tl_object_ptrw_, photo->h_); + s.dimensions = get_dimensions(photo->w_, photo->h_, "get_encrypted_file_photo"); s.size = photo->size_; s.file_id = file_id; res.photos.push_back(s); @@ -725,7 +725,7 @@ Photo get_photo(FileManager *file_manager, tl_object_ptr && for (auto &size_ptr : photo->sizes_) { auto photo_size = get_photo_size(file_manager, {FileType::Photo, 0}, photo->id_, photo->access_hash_, photo->file_reference_.as_slice().str(), dc_id, owner_dialog_id, - std::move(size_ptr), PhotoFormat::Jpeg, true); + std::move(size_ptr), PhotoFormat::Jpeg); if (photo_size.get_offset() == 0) { PhotoSize &size = photo_size.get<0>(); if (size.type == 0 || size.type == 't' || size.type == 'i' || size.type == 'u' || size.type == 'v') { @@ -842,7 +842,7 @@ tl_object_ptr photo_get_input_media(FileManager *file_ if (ttl != 0) { flags |= telegram_api::inputMediaPhotoExternal::TTL_SECONDS_MASK; } - LOG(INFO) << "Create inputMediaPhotoExternal with a URL " << file_view.url() << " and ttl " << ttl; + LOG(INFO) << "Create inputMediaPhotoExternal with a URL " << file_view.url() << " and TTL " << ttl; return make_tl_object(flags, file_view.url(), ttl); } if (input_file == nullptr) { diff --git a/td/telegram/Photo.h b/td/telegram/Photo.h index 2662e5367..e8ddfed1e 100644 --- a/td/telegram/Photo.h +++ b/td/telegram/Photo.h @@ -71,7 +71,7 @@ struct Photo { } }; -Dimensions get_dimensions(int32 width, int32 height); +Dimensions get_dimensions(int32 width, int32 height, const char *source); bool operator==(const Dimensions &lhs, const Dimensions &rhs); bool operator!=(const Dimensions &lhs, const Dimensions &rhs); @@ -113,7 +113,7 @@ PhotoSize get_secret_thumbnail_photo_size(FileManager *file_manager, BufferSlice Variant get_photo_size(FileManager *file_manager, PhotoSizeSource source, int64 id, int64 access_hash, string file_reference, DcId dc_id, DialogId owner_dialog_id, tl_object_ptr &&size_ptr, - PhotoFormat format, bool expect_jpeg_minithumbnail); + PhotoFormat format); AnimationSize get_animation_size(FileManager *file_manager, PhotoSizeSource source, int64 id, int64 access_hash, string file_reference, DcId dc_id, DialogId owner_dialog_id, tl_object_ptr &&size); diff --git a/td/telegram/PollManager.cpp b/td/telegram/PollManager.cpp index 5b9097ec6..4081f7875 100644 --- a/td/telegram/PollManager.cpp +++ b/td/telegram/PollManager.cpp @@ -224,7 +224,7 @@ class StopPollActor : public NetActorOnce { } auto result = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for StopPollQuery: " << to_string(result); + LOG(INFO) << "Receive result for StopPoll: " << to_string(result); td->updates_manager_->on_get_updates(std::move(result), std::move(promise_)); } diff --git a/td/telegram/ReportReason.cpp b/td/telegram/ReportReason.cpp new file mode 100644 index 000000000..5d983f67c --- /dev/null +++ b/td/telegram/ReportReason.cpp @@ -0,0 +1,97 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/ReportReason.h" + +#include "td/telegram/misc.h" + +namespace td { + +Result ReportReason::get_report_reason(td_api::object_ptr reason, + string &&message) { + if (!clean_input_string(message)) { + return Status::Error(400, "Report text must be encoded in UTF-8"); + } + if (reason == nullptr) { + return Status::Error(400, "Reason must be non-empty"); + } + + auto type = [&] { + switch (reason->get_id()) { + case td_api::chatReportReasonSpam::ID: + return ReportReason::Type::Spam; + case td_api::chatReportReasonViolence::ID: + return ReportReason::Type::Violence; + case td_api::chatReportReasonPornography::ID: + return ReportReason::Type::Pornography; + case td_api::chatReportReasonChildAbuse::ID: + return ReportReason::Type::ChildAbuse; + case td_api::chatReportReasonCopyright::ID: + return ReportReason::Type::Copyright; + case td_api::chatReportReasonUnrelatedLocation::ID: + return ReportReason::Type::UnrelatedLocation; + case td_api::chatReportReasonFake::ID: + return ReportReason::Type::Fake; + case td_api::chatReportReasonCustom::ID: + return ReportReason::Type::Custom; + default: + UNREACHABLE(); + return ReportReason::Type::Custom; + } + }(); + return ReportReason(type, std::move(message)); +} + +tl_object_ptr ReportReason::get_input_report_reason() const { + switch (type_) { + case ReportReason::Type::Spam: + return make_tl_object(); + case ReportReason::Type::Violence: + return make_tl_object(); + case ReportReason::Type::Pornography: + return make_tl_object(); + case ReportReason::Type::ChildAbuse: + return make_tl_object(); + case ReportReason::Type::Copyright: + return make_tl_object(); + case ReportReason::Type::UnrelatedLocation: + return make_tl_object(); + case ReportReason::Type::Fake: + return make_tl_object(); + case ReportReason::Type::Custom: + return make_tl_object(); + default: + UNREACHABLE(); + return nullptr; + } +} + +StringBuilder &operator<<(StringBuilder &string_builder, const ReportReason &report_reason) { + string_builder << "ReportReason"; + switch (report_reason.type_) { + case ReportReason::Type::Spam: + return string_builder << "Spam"; + case ReportReason::Type::Violence: + return string_builder << "Violence"; + case ReportReason::Type::Pornography: + return string_builder << "Pornography"; + case ReportReason::Type::ChildAbuse: + return string_builder << "ChildAbuse"; + case ReportReason::Type::Copyright: + return string_builder << "Copyright"; + case ReportReason::Type::UnrelatedLocation: + return string_builder << "UnrelatedLocation"; + case ReportReason::Type::Fake: + return string_builder << "Fake"; + case ReportReason::Type::Custom: + return string_builder << "Custom"; + default: + UNREACHABLE(); + } + return string_builder; +} + +} // namespace td diff --git a/td/telegram/ReportReason.h b/td/telegram/ReportReason.h new file mode 100644 index 000000000..43410d62b --- /dev/null +++ b/td/telegram/ReportReason.h @@ -0,0 +1,50 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/Status.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class ReportReason { + enum class Type : int32 { Spam, Violence, Pornography, ChildAbuse, Copyright, UnrelatedLocation, Fake, Custom }; + Type type_ = Type::Spam; + string message_; + + friend StringBuilder &operator<<(StringBuilder &string_builder, const ReportReason &report_reason); + + ReportReason(Type type, string &&message) : type_(type), message_(std::move(message)) { + } + + public: + ReportReason() = default; + + static Result get_report_reason(td_api::object_ptr reason, string &&message); + + tl_object_ptr get_input_report_reason() const; + + const string &get_message() const { + return message_; + } + + bool is_spam() const { + return type_ == Type::Spam; + } + + bool is_unrelated_location() const { + return type_ == Type::UnrelatedLocation; + } +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const ReportReason &report_reason); + +} // namespace td diff --git a/td/telegram/SecretChatActor.cpp b/td/telegram/SecretChatActor.cpp index ea88cb424..cf290178c 100644 --- a/td/telegram/SecretChatActor.cpp +++ b/td/telegram/SecretChatActor.cpp @@ -115,7 +115,7 @@ void SecretChatActor::on_result_resendable(NetQueryPtr net_query, Promiseid()); if (close_flag_) { if (key == static_cast(QueryType::DiscardEncryption)) { - on_discard_encryption_result(std::move(net_query)); + discard_encryption_promise_.set_value(Unit()); } return; } @@ -140,7 +140,7 @@ void SecretChatActor::on_result_resendable(NetQueryPtr net_query, Promise event) { - do_close_chat_impl(std::move(event)); + do_close_chat_impl(event->delete_history, event->is_already_discarded, event->log_event_id(), Promise()); } void SecretChatActor::replay_create_chat(unique_ptr event) { @@ -709,10 +709,10 @@ void SecretChatActor::check_status(Status status) { void SecretChatActor::on_fatal_error(Status status) { LOG(ERROR) << "Fatal error: " << status; - cancel_chat(Promise<>()); + cancel_chat(false, false, Promise<>()); } -void SecretChatActor::cancel_chat(Promise<> promise) { +void SecretChatActor::cancel_chat(bool delete_history, bool is_already_discarded, Promise<> promise) { if (close_flag_) { promise.set_value(Unit()); return; @@ -735,37 +735,73 @@ void SecretChatActor::cancel_chat(Promise<> promise) { auto event = make_unique(); event->chat_id = auth_state_.id; - event->set_log_event_id(binlog_add(context_->binlog(), LogEvent::HandlerType::SecretChats, create_storer(*event))); + auto log_event_id = binlog_add(context_->binlog(), LogEvent::HandlerType::SecretChats, create_storer(*event)); - auto on_sync = PromiseCreator::lambda( - [actor_id = actor_id(this), event = std::move(event), promise = std::move(promise)](Result result) mutable { - if (result.is_ok()) { - send_closure(actor_id, &SecretChatActor::do_close_chat_impl, std::move(event)); - promise.set_value(Unit()); - } else { - promise.set_error(result.error().clone()); - send_closure(actor_id, &SecretChatActor::on_promise_error, result.move_as_error(), "do_close_chat_impl"); - } - }); + auto on_sync = PromiseCreator::lambda([actor_id = actor_id(this), delete_history, is_already_discarded, log_event_id, + promise = std::move(promise)](Result result) mutable { + if (result.is_ok()) { + send_closure(actor_id, &SecretChatActor::do_close_chat_impl, delete_history, is_already_discarded, log_event_id, + std::move(promise)); + } else { + promise.set_error(result.error().clone()); + send_closure(actor_id, &SecretChatActor::on_promise_error, result.move_as_error(), "cancel_chat"); + } + }); context_->binlog()->force_sync(std::move(on_sync)); yield(); } -void SecretChatActor::do_close_chat_impl(unique_ptr event) { +void SecretChatActor::do_close_chat_impl(bool delete_history, bool is_already_discarded, uint64 log_event_id, + Promise &&promise) { close_flag_ = true; - close_log_event_id_ = event->log_event_id(); - LOG(INFO) << "Send messages.discardEncryption"; auth_state_.state = State::Closed; context_->secret_chat_db()->set_value(auth_state_); context_->secret_chat_db()->erase_value(config_state_); context_->secret_chat_db()->erase_value(pfs_state_); context_->secret_chat_db()->erase_value(seq_no_state_); - auto query = create_net_query(QueryType::DiscardEncryption, telegram_api::messages_discardEncryption(auth_state_.id)); + + MultiPromiseActorSafe mpas{"DeleteMessagesFromServerMultiPromiseActor"}; + mpas.add_promise( + PromiseCreator::lambda([actor_id = actor_id(this), log_event_id, promise = std::move(promise)](Unit) mutable { + send_closure(actor_id, &SecretChatActor::on_closed, log_event_id, std::move(promise)); + })); + + auto lock = mpas.get_promise(); + + if (delete_history) { + context_->on_flush_history(true, MessageId::max(), mpas.get_promise()); + } send_update_secret_chat(); - context_->send_net_query(std::move(query), actor_shared(this), true); + if (!is_already_discarded) { + int32 flags = 0; + if (delete_history) { + flags |= telegram_api::messages_discardEncryption::DELETE_HISTORY_MASK; + } + auto query = create_net_query(QueryType::DiscardEncryption, + telegram_api::messages_discardEncryption(flags, false /*ignored*/, auth_state_.id)); + query->total_timeout_limit_ = 60 * 60 * 24 * 365; + context_->send_net_query(std::move(query), actor_shared(this), true); + discard_encryption_promise_ = mpas.get_promise(); + } + + lock.set_value(Unit()); +} + +void SecretChatActor::on_closed(uint64 log_event_id, Promise &&promise) { + CHECK(close_flag_); + if (context_->close_flag()) { + return; + } + + LOG(INFO) << "Finish closing"; + context_->secret_chat_db()->erase_value(auth_state_); + binlog_erase(context_->binlog(), log_event_id); + promise.set_value(Unit()); + // skip flush + stop(); } void SecretChatActor::do_create_chat_impl(unique_ptr event) { @@ -788,18 +824,6 @@ void SecretChatActor::do_create_chat_impl(unique_ptrclose_flag()) { - return; - } - LOG(INFO) << "Got result for messages.discardEncryption"; - context_->secret_chat_db()->erase_value(auth_state_); - binlog_erase(context_->binlog(), close_log_event_id_); - // skip flush - stop(); -} telegram_api::object_ptr SecretChatActor::get_input_user() { return telegram_api::make_object(auth_state_.user_id, auth_state_.user_access_hash); @@ -1325,7 +1349,8 @@ Status SecretChatActor::do_inbound_message_decrypted(unique_ptron_flush_history(MessageId(ServerMessageId(message->message_id)), std::move(save_message_finish)); + context_->on_flush_history(false, MessageId(ServerMessageId(message->message_id)), + std::move(save_message_finish)); break; case secret_api::decryptedMessageActionReadMessages::ID: { const auto &random_ids = @@ -1899,7 +1924,8 @@ Status SecretChatActor::on_update_chat(telegram_api::encryptedChat &update) { return Status::OK(); } Status SecretChatActor::on_update_chat(telegram_api::encryptedChatDiscarded &update) { - return Status::Error("Chat discarded"); + cancel_chat(update.history_deleted_, true, Promise()); + return Status::OK(); } Status SecretChatActor::on_update_chat(NetQueryPtr query) { @@ -2015,7 +2041,7 @@ void SecretChatActor::calc_key_hash() { auto sha256_slice = MutableSlice(sha256_buf, 32); sha256(pfs_state_.auth_key.key(), sha256_slice); - auth_state_.key_hash = sha1_slice.truncate(16).str() + sha256_slice.truncate(20).str(); + auth_state_.key_hash = PSTRING() << sha1_slice.substr(0, 16) << sha256_slice.substr(0, 20); } void SecretChatActor::send_update_secret_chat() { diff --git a/td/telegram/SecretChatActor.h b/td/telegram/SecretChatActor.h index 6560ad85f..e3e29bdd5 100644 --- a/td/telegram/SecretChatActor.h +++ b/td/telegram/SecretChatActor.h @@ -55,7 +55,8 @@ class SecretChatActor : public NetQueryCallback { VIDEO_NOTES_LAYER = 66, MTPROTO_2_LAYER = 73, NEW_ENTITIES_LAYER = 101, - MY_LAYER = NEW_ENTITIES_LAYER + DELETE_MESSAGES_ON_CLOSE_LAYER = 123, + MY_LAYER = DELETE_MESSAGES_ON_CLOSE_LAYER }; class Context { @@ -96,7 +97,7 @@ class SecretChatActor : public NetQueryCallback { tl_object_ptr file, tl_object_ptr message, Promise<> promise) = 0; virtual void on_delete_messages(std::vector random_id, Promise<> promise) = 0; - virtual void on_flush_history(MessageId message_id, Promise<> promise) = 0; + virtual void on_flush_history(bool remove_from_dialog_list, MessageId message_id, Promise<> promise) = 0; virtual void on_read_message(int64 random_id, Promise<> promise) = 0; virtual void on_screenshot_taken(UserId user_id, MessageId message_id, int32 date, int64 random_id, Promise<> promise) = 0; @@ -112,10 +113,11 @@ class SecretChatActor : public NetQueryCallback { SecretChatActor(int32 id, unique_ptr context, bool can_be_empty); - // First query to new chat must be on of these two + // First query to new chat must be one of these two void update_chat(telegram_api::object_ptr chat); void create_chat(int32 user_id, int64 user_access_hash, int32 random_id, Promise promise); - void cancel_chat(Promise<> promise); + + void cancel_chat(bool delete_history, bool is_already_discarded, Promise<> promise); // Inbound messages // Logevent is created by SecretChatsManager, because it must contain qts @@ -461,7 +463,7 @@ class SecretChatActor : public NetQueryCallback { bool binlog_replay_finish_flag_ = false; bool close_flag_ = false; - LogEvent::Id close_log_event_id_ = 0; + Promise discard_encryption_promise_; LogEvent::Id create_log_event_id_ = 0; @@ -637,8 +639,8 @@ class SecretChatActor : public NetQueryCallback { // DiscardEncryption void on_fatal_error(Status status); - void do_close_chat_impl(unique_ptr event); - void on_discard_encryption_result(NetQueryPtr result); + void do_close_chat_impl(bool delete_history, bool is_already_discarded, uint64 log_event_id, Promise &&promise); + void on_closed(uint64 log_event_id, Promise &&promise); // Other template diff --git a/td/telegram/SecretChatsManager.cpp b/td/telegram/SecretChatsManager.cpp index e82b0b806..db6d0a28e 100644 --- a/td/telegram/SecretChatsManager.cpp +++ b/td/telegram/SecretChatsManager.cpp @@ -106,10 +106,10 @@ void SecretChatsManager::create_chat(int32 user_id, int64 user_access_hash, Prom send_closure(actor, &SecretChatActor::create_chat, user_id, user_access_hash, random_id, std::move(promise)); } -void SecretChatsManager::cancel_chat(SecretChatId secret_chat_id, Promise<> promise) { +void SecretChatsManager::cancel_chat(SecretChatId secret_chat_id, bool delete_history, Promise<> promise) { auto actor = get_chat_actor(secret_chat_id.get()); auto safe_promise = SafePromise<>(std::move(promise), Unit()); - send_closure(actor, &SecretChatActor::cancel_chat, std::move(safe_promise)); + send_closure(actor, &SecretChatActor::cancel_chat, delete_history, false, std::move(safe_promise)); } void SecretChatsManager::send_message(SecretChatId secret_chat_id, tl_object_ptr message, @@ -235,8 +235,9 @@ void SecretChatsManager::replay_binlog_event(BinlogEvent &&binlog_event) { case log_event::SecretChatEvent::Type::CreateSecretChat: return replay_create_chat( unique_ptr(static_cast(message.release()))); + default: + LOG(FATAL) << "Unknown log event type " << tag("type", format::as_hex(static_cast(message->get_type()))); } - LOG(FATAL) << "Unknown log event type " << tag("type", format::as_hex(static_cast(message->get_type()))); } void SecretChatsManager::binlog_replay_finish() { @@ -372,9 +373,9 @@ unique_ptr SecretChatsManager::make_secret_chat_contex send_closure(G()->messages_manager(), &MessagesManager::delete_secret_messages, secret_chat_id_, std::move(random_ids), std::move(promise)); } - void on_flush_history(MessageId message_id, Promise<> promise) override { - send_closure(G()->messages_manager(), &MessagesManager::delete_secret_chat_history, secret_chat_id_, message_id, - std::move(promise)); + void on_flush_history(bool remove_from_dialog_list, MessageId message_id, Promise<> promise) override { + send_closure(G()->messages_manager(), &MessagesManager::delete_secret_chat_history, secret_chat_id_, + remove_from_dialog_list, message_id, std::move(promise)); } void on_read_message(int64 random_id, Promise<> promise) override { send_closure(G()->messages_manager(), &MessagesManager::open_secret_message, secret_chat_id_, random_id, diff --git a/td/telegram/SecretChatsManager.h b/td/telegram/SecretChatsManager.h index 655e9545d..04bf59870 100644 --- a/td/telegram/SecretChatsManager.h +++ b/td/telegram/SecretChatsManager.h @@ -30,13 +30,12 @@ class SecretChatsManager : public Actor { public: explicit SecretChatsManager(ActorShared<> parent); - // Proxy query to corrensponding SecretChatActor. - // Look for more info in SecretChatActor.h + // Proxy query to corrensponding SecretChatActor void on_update_chat(tl_object_ptr update); void on_new_message(tl_object_ptr &&message_ptr, Promise &&promise); void create_chat(int32 user_id, int64 user_access_hash, Promise promise); - void cancel_chat(SecretChatId, Promise<> promise); + void cancel_chat(SecretChatId secret_chat_id, bool delete_history, Promise<> promise); void send_message(SecretChatId secret_chat_id, tl_object_ptr message, tl_object_ptr file, Promise<> promise); void send_message_action(SecretChatId secret_chat_id, tl_object_ptr action); diff --git a/td/telegram/SecureManager.cpp b/td/telegram/SecureManager.cpp index 89d49660a..79b196fdb 100644 --- a/td/telegram/SecureManager.cpp +++ b/td/telegram/SecureManager.cpp @@ -546,7 +546,7 @@ void SetSecureValue::start_upload(FileManager *file_manager, FileId &file_id, Se auto download_file_id = file_manager->dup_file_id(file_id); file_id = file_manager - ->register_generate(FileType::Secure, FileLocationSource::FromServer, file_view.suggested_name(), + ->register_generate(FileType::Secure, FileLocationSource::FromServer, file_view.suggested_path(), PSTRING() << "#file_id#" << download_file_id.get(), DialogId(), file_view.size()) .ok(); } diff --git a/td/telegram/SecureValue.cpp b/td/telegram/SecureValue.cpp index d57318b1f..09376b4fd 100644 --- a/td/telegram/SecureValue.cpp +++ b/td/telegram/SecureValue.cpp @@ -434,7 +434,7 @@ static td_api::object_ptr get_dated_file_object(FileManager * file_view.remote_location().get_access_hash(), file_view.remote_location().get_dc_id(), ""), FileLocationSource::FromServer, DialogId(), file_view.size(), - file_view.expected_size(), file_view.suggested_name()); + file_view.expected_size(), file_view.suggested_path()); return get_dated_file_object(file_manager, dated_file); } diff --git a/td/telegram/StickersManager.cpp b/td/telegram/StickersManager.cpp index 7f9f7229e..6e86cbbc5 100644 --- a/td/telegram/StickersManager.cpp +++ b/td/telegram/StickersManager.cpp @@ -1341,7 +1341,8 @@ tl_object_ptr StickersManager::get_mask_point_object(int32 po } } -vector> StickersManager::get_sticker_minithumbnail(CSlice path) { +vector> StickersManager::get_sticker_minithumbnail( + CSlice path, StickerSetId sticker_set_id, int64 document_id) { if (path.empty()) { return {}; } @@ -1435,7 +1436,7 @@ vector> StickersManager::get_sticke while (!is_closed) { skip_commas(); if (path[pos] == '\0') { - LOG(ERROR) << "Receive unclosed path " << path; + LOG(ERROR) << "Receive unclosed path " << path << " in a sticker " << document_id << " from " << sticker_set_id; return {}; } if (is_alpha(path[pos])) { @@ -1522,7 +1523,8 @@ vector> StickersManager::get_sticke is_closed = true; break; default: - LOG(ERROR) << "Receive invalid command " << command << " at pos " << pos << " in " << path; + LOG(ERROR) << "Receive invalid command " << command << " at pos " << pos << " in a sticker " << document_id + << " from " << sticker_set_id << ": " << path; return {}; } } @@ -1602,18 +1604,31 @@ tl_object_ptr StickersManager::get_sticker_object(FileId file_i const PhotoSize &thumbnail = sticker->m_thumbnail.file_id.is_valid() ? sticker->m_thumbnail : sticker->s_thumbnail; auto thumbnail_format = PhotoFormat::Webp; + int64 document_id = -1; if (!sticker->set_id.is_valid()) { - auto file_view = td_->file_manager_->get_file_view(sticker->file_id); - if (file_view.is_encrypted()) { + auto sticker_file_view = td_->file_manager_->get_file_view(sticker->file_id); + if (sticker_file_view.is_encrypted()) { // uploaded to secret chats stickers have JPEG thumbnail instead of server-generated WEBP thumbnail_format = PhotoFormat::Jpeg; + } else { + if (sticker_file_view.has_remote_location() && sticker_file_view.remote_location().is_document()) { + document_id = sticker_file_view.remote_location().get_id(); + } + + if (thumbnail.file_id.is_valid()) { + auto thumbnail_file_view = td_->file_manager_->get_file_view(thumbnail.file_id); + if (ends_with(thumbnail_file_view.suggested_path(), ".jpg")) { + thumbnail_format = PhotoFormat::Jpeg; + } + } } } auto thumbnail_object = get_thumbnail_object(td_->file_manager_.get(), thumbnail, thumbnail_format); - return make_tl_object(sticker->set_id.get(), sticker->dimensions.width, sticker->dimensions.height, - sticker->alt, sticker->is_animated, sticker->is_mask, std::move(mask_position), - get_sticker_minithumbnail(sticker->minithumbnail), std::move(thumbnail_object), - td_->file_manager_->get_file_object(file_id)); + return make_tl_object( + sticker->set_id.get(), sticker->dimensions.width, sticker->dimensions.height, sticker->alt, sticker->is_animated, + sticker->is_mask, std::move(mask_position), + get_sticker_minithumbnail(sticker->minithumbnail, sticker->set_id, document_id), std::move(thumbnail_object), + td_->file_manager_->get_file_object(file_id)); } tl_object_ptr StickersManager::get_stickers_object(const vector &sticker_ids) const { @@ -1719,9 +1734,9 @@ tl_object_ptr StickersManager::get_sticker_set_object(Sticke sticker_set->is_animated ? PhotoFormat::Tgs : PhotoFormat::Webp); return make_tl_object( sticker_set->id.get(), sticker_set->title, sticker_set->short_name, std::move(thumbnail), - get_sticker_minithumbnail(sticker_set->minithumbnail), sticker_set->is_installed && !sticker_set->is_archived, - sticker_set->is_archived, sticker_set->is_official, sticker_set->is_animated, sticker_set->is_masks, - sticker_set->is_viewed, std::move(stickers), std::move(emojis)); + get_sticker_minithumbnail(sticker_set->minithumbnail, sticker_set->id, -2), + sticker_set->is_installed && !sticker_set->is_archived, sticker_set->is_archived, sticker_set->is_official, + sticker_set->is_animated, sticker_set->is_masks, sticker_set->is_viewed, std::move(stickers), std::move(emojis)); } tl_object_ptr StickersManager::get_sticker_sets_object(int32 total_count, @@ -1765,9 +1780,9 @@ tl_object_ptr StickersManager::get_sticker_set_info_obje sticker_set->is_animated ? PhotoFormat::Tgs : PhotoFormat::Webp); return make_tl_object( sticker_set->id.get(), sticker_set->title, sticker_set->short_name, std::move(thumbnail), - get_sticker_minithumbnail(sticker_set->minithumbnail), sticker_set->is_installed && !sticker_set->is_archived, - sticker_set->is_archived, sticker_set->is_official, sticker_set->is_animated, sticker_set->is_masks, - sticker_set->is_viewed, + get_sticker_minithumbnail(sticker_set->minithumbnail, sticker_set->id, -3), + sticker_set->is_installed && !sticker_set->is_archived, sticker_set->is_archived, sticker_set->is_official, + sticker_set->is_animated, sticker_set->is_masks, sticker_set->is_viewed, sticker_set->was_loaded ? narrow_cast(sticker_set->sticker_ids.size()) : sticker_set->sticker_count, std::move(stickers)); } @@ -1842,8 +1857,19 @@ FileId StickersManager::on_get_sticker(unique_ptr new_sticker, bool rep return file_id; } -bool StickersManager::has_webp_thumbnail(const tl_object_ptr &sticker) { - // server tries to always replace user-provided thumbnail with server-side webp thumbnail +bool StickersManager::has_webp_thumbnail(const vector> &thumbnails) { + // server tries to always replace user-provided thumbnail with server-side WEBP thumbnail + // but there can be some old sticker documents or some big stickers + for (auto &size : thumbnails) { + switch (size->get_id()) { + case telegram_api::photoStrippedSize::ID: + case telegram_api::photoSizeProgressive::ID: + // WEBP thumbnail can't have stripped size or be progressive + return false; + default: + break; + } + } return true; } @@ -1869,7 +1895,7 @@ std::pair StickersManager::on_get_sticker_document( switch (attribute->get_id()) { case telegram_api::documentAttributeImageSize::ID: { auto image_size = move_tl_object_as(attribute); - dimensions = get_dimensions(image_size->w_, image_size->h_); + dimensions = get_dimensions(image_size->w_, image_size->h_, "sticker documentAttributeImageSize"); break; } case telegram_api::documentAttributeSticker::ID: @@ -1896,18 +1922,20 @@ std::pair StickersManager::on_get_sticker_document( PhotoSize thumbnail; string minithumbnail; + auto thumbnail_format = has_webp_thumbnail(document->thumbs_) ? PhotoFormat::Webp : PhotoFormat::Jpeg; for (auto &thumb : document->thumbs_) { - auto photo_size = - get_photo_size(td_->file_manager_.get(), {FileType::Thumbnail, 0}, document_id, document->access_hash_, - document->file_reference_.as_slice().str(), dc_id, DialogId(), std::move(thumb), - has_webp_thumbnail(sticker) ? PhotoFormat::Webp : PhotoFormat::Jpeg, false); + auto photo_size = get_photo_size(td_->file_manager_.get(), {FileType::Thumbnail, 0}, document_id, + document->access_hash_, document->file_reference_.as_slice().str(), dc_id, + DialogId(), std::move(thumb), thumbnail_format); if (photo_size.get_offset() == 0) { if (!thumbnail.file_id.is_valid()) { thumbnail = std::move(photo_size.get<0>()); } break; } else { - minithumbnail = std::move(photo_size.get<1>()); + if (thumbnail_format == PhotoFormat::Webp) { + minithumbnail = std::move(photo_size.get<1>()); + } } } @@ -2414,8 +2442,8 @@ tl_object_ptr StickersManager::get_input_media( } auto mime_type = get_sticker_mime_type(s); if (!s->is_animated && !s->set_id.is_valid()) { - auto suggested_name = file_view.suggested_name(); - const PathView path_view(suggested_name); + auto suggested_path = file_view.suggested_path(); + const PathView path_view(suggested_path); if (path_view.extension() == "tgs") { mime_type = "application/x-tgsticker"; } @@ -2447,7 +2475,7 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptrthumbs_) { auto photo_size = get_photo_size(td_->file_manager_.get(), {set_id.get(), s->access_hash}, 0, 0, "", DcId::create(set->thumb_dc_id_), DialogId(), std::move(thumb), - is_animated ? PhotoFormat::Tgs : PhotoFormat::Webp, false); + is_animated ? PhotoFormat::Tgs : PhotoFormat::Webp); if (photo_size.get_offset() == 0) { if (!thumbnail.file_id.is_valid()) { thumbnail = std::move(photo_size.get<0>()); @@ -4608,7 +4636,8 @@ Result> StickersManager::prepare_input_file if (is_animated) { int32 width = for_thumbnail ? 100 : 512; - create_sticker(file_id, string(), PhotoSize(), get_dimensions(width, width), nullptr, true, nullptr); + create_sticker(file_id, string(), PhotoSize(), get_dimensions(width, width, "prepare_input_file"), nullptr, true, + nullptr); } else { td_->documents_manager_->create_document(file_id, string(), PhotoSize(), "sticker.png", "image/png", false); } diff --git a/td/telegram/StickersManager.h b/td/telegram/StickersManager.h index ed019fcb9..ae5fc1ac6 100644 --- a/td/telegram/StickersManager.h +++ b/td/telegram/StickersManager.h @@ -94,7 +94,7 @@ class StickersManager : public Actor { vector get_installed_sticker_sets(bool is_masks, Promise &&promise); - bool has_webp_thumbnail(const tl_object_ptr &sticker); + static bool has_webp_thumbnail(const vector> &thumbnails); StickerSetId get_sticker_set_id(const tl_object_ptr &set_ptr); @@ -398,7 +398,9 @@ class StickersManager : public Actor { class UploadStickerFileCallback; - static vector> get_sticker_minithumbnail(CSlice path); + static vector> get_sticker_minithumbnail(CSlice path, + StickerSetId sticker_set_id, + int64 document_id); static tl_object_ptr get_mask_point_object(int32 point); diff --git a/td/telegram/SuggestedAction.cpp b/td/telegram/SuggestedAction.cpp index dea54ca2a..cae588b1f 100644 --- a/td/telegram/SuggestedAction.cpp +++ b/td/telegram/SuggestedAction.cpp @@ -6,50 +6,136 @@ // #include "td/telegram/SuggestedAction.h" +#include "td/telegram/ChannelId.h" +#include "td/telegram/Global.h" +#include "td/telegram/Td.h" + +#include "td/actor/actor.h" + +#include "td/utils/algorithm.h" + +#include + namespace td { -SuggestedAction get_suggested_action(Slice action_str) { - if (action_str == Slice("AUTOARCHIVE_POPULAR")) { - return SuggestedAction::EnableArchiveAndMuteNewChats; - } - return SuggestedAction::Empty; +void SuggestedAction::init(Type type) { + type_ = type; } -string get_suggested_action_str(SuggestedAction action) { - switch (action) { - case SuggestedAction::EnableArchiveAndMuteNewChats: +SuggestedAction::SuggestedAction(Slice action_str) { + if (action_str == Slice("AUTOARCHIVE_POPULAR")) { + init(Type::EnableArchiveAndMuteNewChats); + } else if (action_str == Slice("NEWCOMER_TICKS")) { + init(Type::SeeTicksHint); + } +} + +SuggestedAction::SuggestedAction(Slice action_str, DialogId dialog_id) { + CHECK(dialog_id.is_valid()); + if (action_str == Slice("CONVERT_GIGAGROUP")) { + type_ = Type::ConvertToGigagroup; + dialog_id_ = dialog_id; + } +} + +SuggestedAction::SuggestedAction(const td_api::object_ptr &suggested_action) { + if (suggested_action == nullptr) { + return; + } + switch (suggested_action->get_id()) { + case td_api::suggestedActionEnableArchiveAndMuteNewChats::ID: + init(Type::EnableArchiveAndMuteNewChats); + break; + case td_api::suggestedActionCheckPhoneNumber::ID: + init(Type::CheckPhoneNumber); + break; + case td_api::suggestedActionSeeTicksHint::ID: + init(Type::SeeTicksHint); + break; + case td_api::suggestedActionConvertToBroadcastGroup::ID: { + auto action = static_cast(suggested_action.get()); + ChannelId channel_id(action->supergroup_id_); + if (channel_id.is_valid()) { + type_ = Type::ConvertToGigagroup; + dialog_id_ = DialogId(channel_id); + } + break; + } + default: + UNREACHABLE(); + } +} + +string SuggestedAction::get_suggested_action_str() const { + switch (type_) { + case Type::EnableArchiveAndMuteNewChats: return "AUTOARCHIVE_POPULAR"; + case Type::SeeTicksHint: + return "NEWCOMER_TICKS"; + case Type::ConvertToGigagroup: + return "CONVERT_GIGAGROUP"; default: return string(); } } -SuggestedAction get_suggested_action(const td_api::object_ptr &action_object) { - if (action_object == nullptr) { - return SuggestedAction::Empty; - } - switch (action_object->get_id()) { - case td_api::suggestedActionEnableArchiveAndMuteNewChats::ID: - return SuggestedAction::EnableArchiveAndMuteNewChats; - case td_api::suggestedActionCheckPhoneNumber::ID: - return SuggestedAction::CheckPhoneNumber; +td_api::object_ptr SuggestedAction::get_suggested_action_object() const { + switch (type_) { + case Type::Empty: + return nullptr; + case Type::EnableArchiveAndMuteNewChats: + return td_api::make_object(); + case Type::CheckPhoneNumber: + return td_api::make_object(); + case Type::SeeTicksHint: + return td_api::make_object(); + case Type::ConvertToGigagroup: + return td_api::make_object(dialog_id_.get_channel_id().get()); default: UNREACHABLE(); - return SuggestedAction::Empty; + return nullptr; } } -td_api::object_ptr get_suggested_action_object(SuggestedAction action) { - switch (action) { - case SuggestedAction::Empty: - return nullptr; - case SuggestedAction::EnableArchiveAndMuteNewChats: - return td_api::make_object(); - case SuggestedAction::CheckPhoneNumber: - return td_api::make_object(); - default: - UNREACHABLE(); - return nullptr; +td_api::object_ptr get_update_suggested_actions_object( + const vector &added_actions, const vector &removed_actions) { + auto get_object = [](const SuggestedAction &action) { + return action.get_suggested_action_object(); + }; + return td_api::make_object(transform(added_actions, get_object), + transform(removed_actions, get_object)); +} + +void update_suggested_actions(vector &suggested_actions, + vector &&new_suggested_actions) { + td::unique(new_suggested_actions); + if (new_suggested_actions == suggested_actions) { + return; + } + + vector added_actions; + vector removed_actions; + auto old_it = suggested_actions.begin(); + auto new_it = new_suggested_actions.begin(); + while (old_it != suggested_actions.end() || new_it != new_suggested_actions.end()) { + if (old_it != suggested_actions.end() && (new_it == new_suggested_actions.end() || *old_it < *new_it)) { + removed_actions.push_back(*old_it++); + } else if (old_it == suggested_actions.end() || *new_it < *old_it) { + added_actions.push_back(*new_it++); + } else { + old_it++; + new_it++; + } + } + CHECK(!added_actions.empty() || !removed_actions.empty()); + suggested_actions = std::move(new_suggested_actions); + send_closure(G()->td(), &Td::send_update, + get_update_suggested_actions_object(std::move(added_actions), std::move(removed_actions))); +} + +void remove_suggested_action(vector &suggested_actions, SuggestedAction suggested_action) { + if (td::remove(suggested_actions, suggested_action)) { + send_closure(G()->td(), &Td::send_update, get_update_suggested_actions_object({}, {suggested_action})); } } diff --git a/td/telegram/SuggestedAction.h b/td/telegram/SuggestedAction.h index d588c6b6c..bb8477923 100644 --- a/td/telegram/SuggestedAction.h +++ b/td/telegram/SuggestedAction.h @@ -6,6 +6,7 @@ // #pragma once +#include "td/telegram/DialogId.h" #include "td/telegram/td_api.h" #include "td/utils/common.h" @@ -13,14 +14,53 @@ namespace td { -enum class SuggestedAction : int32 { Empty, EnableArchiveAndMuteNewChats, CheckPhoneNumber }; +struct SuggestedAction { + enum class Type : int32 { Empty, EnableArchiveAndMuteNewChats, CheckPhoneNumber, SeeTicksHint, ConvertToGigagroup }; + Type type_ = Type::Empty; + DialogId dialog_id_; -SuggestedAction get_suggested_action(Slice action_str); + void init(Type type); -string get_suggested_action_str(SuggestedAction action); + SuggestedAction() = default; -SuggestedAction get_suggested_action(const td_api::object_ptr &action_object); + explicit SuggestedAction(Type type, DialogId dialog_id = DialogId()) : type_(type), dialog_id_(dialog_id) { + } -td_api::object_ptr get_suggested_action_object(SuggestedAction action); + explicit SuggestedAction(Slice action_str); + + SuggestedAction(Slice action_str, DialogId dialog_id); + + explicit SuggestedAction(const td_api::object_ptr &suggested_action); + + bool is_empty() const { + return type_ == Type::Empty; + } + + string get_suggested_action_str() const; + + td_api::object_ptr get_suggested_action_object() const; +}; + +inline bool operator==(const SuggestedAction &lhs, const SuggestedAction &rhs) { + CHECK(lhs.dialog_id_ == rhs.dialog_id_); + return lhs.type_ == rhs.type_; +} + +inline bool operator!=(const SuggestedAction &lhs, const SuggestedAction &rhs) { + return !(lhs == rhs); +} + +inline bool operator<(const SuggestedAction &lhs, const SuggestedAction &rhs) { + CHECK(lhs.dialog_id_ == rhs.dialog_id_); + return static_cast(lhs.type_) < static_cast(rhs.type_); +} + +td_api::object_ptr get_update_suggested_actions_object( + const vector &added_actions, const vector &removed_actions); + +void update_suggested_actions(vector &suggested_actions, + vector &&new_suggested_actions); + +void remove_suggested_action(vector &suggested_actions, SuggestedAction suggested_action); } // namespace td diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 8d2257986..0d32b3111 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -80,6 +80,7 @@ #include "td/telegram/PollManager.h" #include "td/telegram/PrivacyManager.h" #include "td/telegram/PublicDialogType.h" +#include "td/telegram/ReportReason.h" #include "td/telegram/RequestActor.h" #include "td/telegram/SecretChatId.h" #include "td/telegram/SecretChatsManager.h" @@ -526,8 +527,6 @@ class SaveAppLogQuery : public Td::ResultHandler { } }; -/*** Td ***/ -/** Td queries **/ class TestQuery : public Td::ResultHandler { public: explicit TestQuery(uint64 request_id) : request_id_(request_id) { @@ -1875,7 +1874,7 @@ class CreateNewSecretChatRequest : public RequestActor { // But since the update may still be on its way, we will update essential fields here. td->contacts_manager_->on_update_secret_chat( secret_chat_id_, 0 /* no access_hash */, user_id_, SecretChatState::Unknown, true /* it is outbound chat */, - -1 /* unknown ttl */, 0 /* unknown creation date */, "" /* no key_hash */, 0, FolderId()); + -1 /* unknown TTL */, 0 /* unknown creation date */, "" /* no key_hash */, 0, FolderId()); DialogId dialog_id(secret_chat_id_); td->messages_manager_->force_create_dialog(dialog_id, "create new secret chat", true); send_result(td->messages_manager_->get_chat_object(dialog_id)); @@ -1892,13 +1891,14 @@ class CreateNewSupergroupChatRequest : public RequestActor<> { bool is_megagroup_; string description_; DialogLocation location_; + bool for_import_; int64 random_id_; DialogId dialog_id_; void do_run(Promise &&promise) override { dialog_id_ = td->messages_manager_->create_new_channel_chat(title_, is_megagroup_, description_, location_, - random_id_, std::move(promise)); + for_import_, random_id_, std::move(promise)); } void do_send_result() override { @@ -1908,12 +1908,14 @@ class CreateNewSupergroupChatRequest : public RequestActor<> { public: CreateNewSupergroupChatRequest(ActorShared td, uint64 request_id, string title, bool is_megagroup, - string description, td_api::object_ptr &&location) + string description, td_api::object_ptr &&location, + bool for_import) : RequestActor(std::move(td), request_id) , title_(std::move(title)) , is_megagroup_(is_megagroup) , description_(std::move(description)) , location_(std::move(location)) + , for_import_(for_import) , random_id_(0) { } }; @@ -1947,7 +1949,7 @@ class GetChatMemberRequest : public RequestActor<> { DialogParticipant dialog_participant_; void do_run(Promise &&promise) override { - dialog_participant_ = td->messages_manager_->get_dialog_participant(dialog_id_, user_id_, random_id_, + dialog_participant_ = td->contacts_manager_->get_dialog_participant(dialog_id_, user_id_, random_id_, get_tries() < 3, std::move(promise)); } @@ -1965,50 +1967,13 @@ class GetChatMemberRequest : public RequestActor<> { } }; -class SearchChatMembersRequest : public RequestActor<> { - DialogId dialog_id_; - string query_; - int32 limit_; - DialogParticipantsFilter filter_; - int64 random_id_ = 0; - - std::pair> participants_; - - void do_run(Promise &&promise) override { - participants_ = td->messages_manager_->search_dialog_participants(dialog_id_, query_, limit_, filter_, random_id_, - false, get_tries() < 3, std::move(promise)); - } - - void do_send_result() override { - // TODO create function get_chat_members_object - vector> result; - result.reserve(participants_.second.size()); - for (auto participant : participants_.second) { - result.push_back(td->contacts_manager_->get_chat_member_object(participant)); - } - - send_result(make_tl_object(participants_.first, std::move(result))); - } - - public: - SearchChatMembersRequest(ActorShared td, uint64 request_id, int64 dialog_id, string &&query, int32 limit, - DialogParticipantsFilter filter) - : RequestActor(std::move(td), request_id) - , dialog_id_(dialog_id) - , query_(std::move(query)) - , limit_(limit) - , filter_(filter) { - set_tries(3); - } -}; - class GetChatAdministratorsRequest : public RequestActor<> { DialogId dialog_id_; vector administrators_; void do_run(Promise &&promise) override { - administrators_ = td->messages_manager_->get_dialog_administrators(dialog_id_, get_tries(), std::move(promise)); + administrators_ = td->contacts_manager_->get_dialog_administrators(dialog_id_, get_tries(), std::move(promise)); } void do_send_result() override { @@ -2026,23 +1991,6 @@ class GetChatAdministratorsRequest : public RequestActor<> { } }; -class GenerateChatInviteLinkRequest : public RequestOnceActor { - DialogId dialog_id_; - - void do_run(Promise &&promise) override { - td->messages_manager_->export_dialog_invite_link(dialog_id_, std::move(promise)); - } - - void do_send_result() override { - send_result(make_tl_object(td->messages_manager_->get_dialog_invite_link(dialog_id_))); - } - - public: - GenerateChatInviteLinkRequest(ActorShared td, uint64 request_id, int64 dialog_id) - : RequestOnceActor(std::move(td), request_id), dialog_id_(dialog_id) { - } -}; - class CheckChatInviteLinkRequest : public RequestActor<> { string invite_link_; @@ -2282,43 +2230,6 @@ class GetRecentInlineBotsRequest : public RequestActor<> { } }; -class GetSupergroupMembersRequest : public RequestActor<> { - ChannelId channel_id_; - tl_object_ptr filter_; - int32 offset_; - int32 limit_; - int64 random_id_ = 0; - - std::pair> participants_; - - void do_run(Promise &&promise) override { - participants_ = td->contacts_manager_->get_channel_participants( - channel_id_, filter_, string(), offset_, limit_, -1, random_id_, false, get_tries() < 3, std::move(promise)); - } - - void do_send_result() override { - // TODO create function get_chat_members_object - vector> result; - result.reserve(participants_.second.size()); - for (auto participant : participants_.second) { - result.push_back(td->contacts_manager_->get_chat_member_object(participant)); - } - - send_result(make_tl_object(participants_.first, std::move(result))); - } - - public: - GetSupergroupMembersRequest(ActorShared td, uint64 request_id, int32 channel_id, - tl_object_ptr &&filter, int32 offset, int32 limit) - : RequestActor(std::move(td), request_id) - , channel_id_(channel_id) - , filter_(std::move(filter)) - , offset_(offset) - , limit_(limit) { - set_tries(3); - } -}; - class GetUserProfilePhotosRequest : public RequestActor<> { UserId user_id_; int32 offset_; @@ -3124,6 +3035,7 @@ void Td::on_alarm_timeout(int64 alarm_id) { updates_manager_->ping_server(); alarm_timeout_.set_timeout_in(PING_SERVER_ALARM_ID, PING_SERVER_TIMEOUT + Random::fast(0, PING_SERVER_TIMEOUT / 5)); + set_is_bot_online(false); } return; } @@ -3231,7 +3143,7 @@ void Td::on_get_promo_data(Resultis_authorized() && !auth_manager_->is_bot()) { LOG(INFO) << "Schedule getPromoData in " << expires_in; alarm_timeout_.set_timeout_in(PROMO_DATA_ALARM_ID, expires_in); @@ -3282,6 +3194,15 @@ bool Td::is_online() const { return is_online_; } +void Td::set_is_bot_online(bool is_bot_online) { + if (is_bot_online == is_bot_online_) { + return; + } + + is_bot_online_ = is_bot_online; + send_closure(G()->state_manager(), &StateManager::on_online, is_bot_online_); +} + bool Td::is_authentication_request(int32 id) { switch (id) { case td_api::setTdlibParameters::ID: @@ -3632,6 +3553,7 @@ void Td::on_result(NetQueryPtr query) { if (auth_manager_->is_bot() && auth_manager_->is_authorized()) { alarm_timeout_.set_timeout_in(PING_SERVER_ALARM_ID, PING_SERVER_TIMEOUT + Random::fast(0, PING_SERVER_TIMEOUT / 5)); + set_is_bot_online(true); } } return; @@ -4292,7 +4214,7 @@ Status Td::init(DbKey key) { on_online_updated(true, true); } if (auth_manager_->is_bot()) { - send_closure(G()->state_manager(), &StateManager::on_online, true); + set_is_bot_online(true); } // Send binlog events to managers @@ -5367,10 +5289,10 @@ void Td::on_request(uint64 id, td_api::optimizeStorage &request) { } void Td::on_request(uint64 id, td_api::getNetworkStatistics &request) { - CREATE_REQUEST_PROMISE(); if (!request.only_current_ && G()->shared_config().get_option_boolean("disable_persistent_network_statistics")) { return send_error_raw(id, 400, "Persistent network statistics is disabled"); } + CREATE_REQUEST_PROMISE(); auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); @@ -5453,23 +5375,23 @@ void Td::on_request(uint64 id, const td_api::getAutoDownloadSettingsPresets &req void Td::on_request(uint64 id, const td_api::setAutoDownloadSettings &request) { CHECK_IS_USER(); - CREATE_OK_REQUEST_PROMISE(); if (request.settings_ == nullptr) { return send_error_raw(id, 400, "New settings must be non-empty"); } + CREATE_OK_REQUEST_PROMISE(); set_auto_download_settings(this, get_net_type(request.type_), get_auto_download_settings(request.settings_), std::move(promise)); } void Td::on_request(uint64 id, td_api::getTopChats &request) { CHECK_IS_USER(); - CREATE_REQUEST_PROMISE(); if (request.category_ == nullptr) { - return promise.set_error(Status::Error(400, "Top chat category must be non-empty")); + return send_error_raw(id, 400, "Top chat category must be non-empty"); } if (request.limit_ <= 0) { - return promise.set_error(Status::Error(400, "Limit must be positive")); + return send_error_raw(id, 400, "Limit must be positive"); } + CREATE_REQUEST_PROMISE(); auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result> result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); @@ -5611,6 +5533,21 @@ void Td::on_request(uint64 id, const td_api::openMessageContent &request) { id, messages_manager_->open_message_content({DialogId(request.chat_id_), MessageId(request.message_id_)})); } +void Td::on_request(uint64 id, td_api::getExternalLink &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.link_); + CREATE_REQUEST_PROMISE(); + auto query_promise = [promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(td_api::make_object(result.ok())); + } + }; + send_closure_later(G()->config_manager(), &ConfigManager::get_external_link, std::move(request.link_), + std::move(query_promise)); +} + void Td::on_request(uint64 id, const td_api::getChatHistory &request) { CHECK_IS_USER(); CREATE_REQUEST(GetChatHistoryRequest, request.chat_id_, request.from_message_id_, request.offset_, request.limit_, @@ -5624,6 +5561,21 @@ void Td::on_request(uint64 id, const td_api::deleteChatHistory &request) { std::move(promise)); } +void Td::on_request(uint64 id, const td_api::deleteChat &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + DialogId dialog_id(request.chat_id_); + auto query_promise = [actor_id = messages_manager_actor_.get(), dialog_id, + promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &MessagesManager::on_dialog_deleted, dialog_id, std::move(promise)); + } + }; + contacts_manager_->delete_dialog(dialog_id, std::move(query_promise)); +} + void Td::on_request(uint64 id, const td_api::getMessageThreadHistory &request) { CHECK_IS_USER(); CREATE_REQUEST(GetMessageThreadHistoryRequest, request.chat_id_, request.message_id_, request.from_message_id_, @@ -5663,6 +5615,12 @@ void Td::on_request(uint64 id, td_api::searchCallMessages &request) { CREATE_REQUEST(SearchCallMessagesRequest, request.from_message_id_, request.limit_, request.only_missed_); } +void Td::on_request(uint64 id, const td_api::deleteAllCallMessages &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->delete_all_call_messages(request.revoke_, std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::searchChatRecentLocationMessages &request) { CHECK_IS_USER(); CREATE_REQUEST(SearchChatRecentLocationMessagesRequest, request.chat_id_, request.limit_); @@ -5789,18 +5747,6 @@ void Td::on_request(uint64 id, td_api::sendInlineQueryResultMessage &request) { messages_manager_->get_message_object({dialog_id, r_new_message_id.ok()})); } -void Td::on_request(uint64 id, const td_api::sendChatSetTtlMessage &request) { - DialogId dialog_id(request.chat_id_); - auto r_new_message_id = messages_manager_->send_dialog_set_ttl_message(dialog_id, request.ttl_); - if (r_new_message_id.is_error()) { - return send_closure(actor_id(this), &Td::send_error, id, r_new_message_id.move_as_error()); - } - - CHECK(r_new_message_id.ok().is_valid()); - send_closure(actor_id(this), &Td::send_result, id, - messages_manager_->get_message_object({dialog_id, r_new_message_id.ok()})); -} - void Td::on_request(uint64 id, td_api::addLocalMessage &request) { CHECK_IS_USER(); @@ -6003,7 +5949,7 @@ void Td::on_request(uint64 id, td_api::createNewSupergroupChat &request) { CLEAN_INPUT_STRING(request.title_); CLEAN_INPUT_STRING(request.description_); CREATE_REQUEST(CreateNewSupergroupChatRequest, std::move(request.title_), !request.is_channel_, - std::move(request.description_), std::move(request.location_)); + std::move(request.description_), std::move(request.location_), request.for_import_); } void Td::on_request(uint64 id, td_api::createNewSecretChat &request) { CREATE_REQUEST(CreateNewSecretChatRequest, request.user_id_); @@ -6011,6 +5957,21 @@ void Td::on_request(uint64 id, td_api::createNewSecretChat &request) { void Td::on_request(uint64 id, const td_api::createCall &request) { CHECK_IS_USER(); + + if (request.protocol_ == nullptr) { + return send_error_raw(id, 400, "Call protocol must be non-empty"); + } + + UserId user_id(request.user_id_); + auto input_user = contacts_manager_->get_input_user(user_id); + if (input_user == nullptr) { + return send_error_raw(id, 400, "User not found"); + } + + if (!G()->shared_config().get_option_boolean("calls_enabled")) { + return send_error_raw(id, 400, "Calls are not enabled for the current user"); + } + CREATE_REQUEST_PROMISE(); auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { if (result.is_error()) { @@ -6019,31 +5980,16 @@ void Td::on_request(uint64 id, const td_api::createCall &request) { promise.set_value(result.ok().get_call_id_object()); } }); - - if (!request.protocol_) { - return query_promise.set_error(Status::Error(5, "Call protocol must be non-empty")); - } - - UserId user_id(request.user_id_); - auto input_user = contacts_manager_->get_input_user(user_id); - if (input_user == nullptr) { - return query_promise.set_error(Status::Error(6, "User not found")); - } - - if (!G()->shared_config().get_option_boolean("calls_enabled")) { - return query_promise.set_error(Status::Error(7, "Calls are not enabled for the current user")); - } - send_closure(G()->call_manager(), &CallManager::create_call, user_id, std::move(input_user), CallProtocol(*request.protocol_), request.is_video_, std::move(query_promise)); } void Td::on_request(uint64 id, const td_api::acceptCall &request) { CHECK_IS_USER(); - CREATE_OK_REQUEST_PROMISE(); - if (!request.protocol_) { - return promise.set_error(Status::Error(5, "Call protocol must be non-empty")); + if (request.protocol_ == nullptr) { + return send_error_raw(id, 400, "Call protocol must be non-empty"); } + CREATE_OK_REQUEST_PROMISE(); send_closure(G()->call_manager(), &CallManager::accept_call, CallId(request.call_id_), CallProtocol(*request.protocol_), std::move(promise)); } @@ -6136,6 +6082,13 @@ void Td::on_request(uint64 id, const td_api::toggleGroupCallParticipantIsMuted & GroupCallId(request.group_call_id_), UserId(request.user_id_), request.is_muted_, std::move(promise)); } +void Td::on_request(uint64 id, const td_api::setGroupCallParticipantVolumeLevel &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + group_call_manager_->set_group_call_participant_volume_level( + GroupCallId(request.group_call_id_), UserId(request.user_id_), request.volume_level_, std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::loadGroupCallParticipants &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); @@ -6233,6 +6186,11 @@ void Td::on_request(uint64 id, const td_api::setChatPhoto &request) { messages_manager_->set_dialog_photo(DialogId(request.chat_id_), request.photo_, std::move(promise)); } +void Td::on_request(uint64 id, const td_api::setChatMessageTtlSetting &request) { + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->set_dialog_message_ttl_setting(DialogId(request.chat_id_), request.ttl_, std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::setChatPermissions &request) { CREATE_OK_REQUEST_PROMISE(); messages_manager_->set_dialog_permissions(DialogId(request.chat_id_), request.permissions_, std::move(promise)); @@ -6328,7 +6286,7 @@ void Td::on_request(uint64 id, const td_api::unpinAllChatMessages &request) { void Td::on_request(uint64 id, const td_api::joinChat &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - messages_manager_->add_dialog_participant(DialogId(request.chat_id_), contacts_manager_->get_my_id(), 0, + contacts_manager_->add_dialog_participant(DialogId(request.chat_id_), contacts_manager_->get_my_id(), 0, std::move(promise)); } @@ -6347,14 +6305,14 @@ void Td::on_request(uint64 id, const td_api::leaveChat &request) { td_api::make_object(status.get_rank(), status.is_anonymous(), false); } } - messages_manager_->set_dialog_participant_status(dialog_id, contacts_manager_->get_my_id(), std::move(new_status), + contacts_manager_->set_dialog_participant_status(dialog_id, contacts_manager_->get_my_id(), std::move(new_status), std::move(promise)); } void Td::on_request(uint64 id, const td_api::addChatMember &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - messages_manager_->add_dialog_participant(DialogId(request.chat_id_), UserId(request.user_id_), + contacts_manager_->add_dialog_participant(DialogId(request.chat_id_), UserId(request.user_id_), request.forward_limit_, std::move(promise)); } @@ -6365,15 +6323,21 @@ void Td::on_request(uint64 id, const td_api::addChatMembers &request) { for (auto &user_id : request.user_ids_) { user_ids.emplace_back(user_id); } - messages_manager_->add_dialog_participants(DialogId(request.chat_id_), user_ids, std::move(promise)); + contacts_manager_->add_dialog_participants(DialogId(request.chat_id_), user_ids, std::move(promise)); } void Td::on_request(uint64 id, td_api::setChatMemberStatus &request) { CREATE_OK_REQUEST_PROMISE(); - messages_manager_->set_dialog_participant_status(DialogId(request.chat_id_), UserId(request.user_id_), + contacts_manager_->set_dialog_participant_status(DialogId(request.chat_id_), UserId(request.user_id_), request.status_, std::move(promise)); } +void Td::on_request(uint64 id, const td_api::banChatMember &request) { + CREATE_OK_REQUEST_PROMISE(); + contacts_manager_->ban_dialog_participant(DialogId(request.chat_id_), UserId(request.user_id_), + request.banned_until_date_, request.revoke_messages_, std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::canTransferOwnership &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); @@ -6390,8 +6354,8 @@ void Td::on_request(uint64 id, const td_api::canTransferOwnership &request) { void Td::on_request(uint64 id, td_api::transferChatOwnership &request) { CHECK_IS_USER(); - CREATE_OK_REQUEST_PROMISE(); CLEAN_INPUT_STRING(request.password_); + CREATE_OK_REQUEST_PROMISE(); contacts_manager_->transfer_dialog_ownership(DialogId(request.chat_id_), UserId(request.user_id_), request.password_, std::move(promise)); } @@ -6402,16 +6366,92 @@ void Td::on_request(uint64 id, const td_api::getChatMember &request) { void Td::on_request(uint64 id, td_api::searchChatMembers &request) { CLEAN_INPUT_STRING(request.query_); - CREATE_REQUEST(SearchChatMembersRequest, request.chat_id_, std::move(request.query_), request.limit_, - get_dialog_participants_filter(request.filter_)); + CREATE_REQUEST_PROMISE(); + auto query_promise = + PromiseCreator::lambda([promise = std::move(promise), td = this](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(result.ok().get_chat_members_object(td)); + } + }); + contacts_manager_->search_dialog_participants(DialogId(request.chat_id_), request.query_, request.limit_, + get_dialog_participants_filter(request.filter_), false, + std::move(query_promise)); } void Td::on_request(uint64 id, td_api::getChatAdministrators &request) { CREATE_REQUEST(GetChatAdministratorsRequest, request.chat_id_); } -void Td::on_request(uint64 id, const td_api::generateChatInviteLink &request) { - CREATE_REQUEST(GenerateChatInviteLinkRequest, request.chat_id_); +void Td::on_request(uint64 id, const td_api::replacePrimaryChatInviteLink &request) { + CREATE_REQUEST_PROMISE(); + contacts_manager_->export_dialog_invite_link(DialogId(request.chat_id_), 0, 0, true, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::createChatInviteLink &request) { + CREATE_REQUEST_PROMISE(); + contacts_manager_->export_dialog_invite_link(DialogId(request.chat_id_), request.expire_date_, request.member_limit_, + false, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::editChatInviteLink &request) { + CLEAN_INPUT_STRING(request.invite_link_); + CREATE_REQUEST_PROMISE(); + contacts_manager_->edit_dialog_invite_link(DialogId(request.chat_id_), request.invite_link_, request.expire_date_, + request.member_limit_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::getChatInviteLink &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.invite_link_); + CREATE_REQUEST_PROMISE(); + contacts_manager_->get_dialog_invite_link(DialogId(request.chat_id_), request.invite_link_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getChatInviteLinkCounts &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + contacts_manager_->get_dialog_invite_link_counts(DialogId(request.chat_id_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::getChatInviteLinks &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.offset_invite_link_); + CREATE_REQUEST_PROMISE(); + contacts_manager_->get_dialog_invite_links(DialogId(request.chat_id_), UserId(request.creator_user_id_), + request.is_revoked_, request.offset_date_, request.offset_invite_link_, + request.limit_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::getChatInviteLinkMembers &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.invite_link_); + CREATE_REQUEST_PROMISE(); + contacts_manager_->get_dialog_invite_link_users(DialogId(request.chat_id_), request.invite_link_, + std::move(request.offset_member_), request.limit_, + std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::revokeChatInviteLink &request) { + CLEAN_INPUT_STRING(request.invite_link_); + CREATE_REQUEST_PROMISE(); + contacts_manager_->revoke_dialog_invite_link(DialogId(request.chat_id_), request.invite_link_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::deleteRevokedChatInviteLink &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.invite_link_); + CREATE_OK_REQUEST_PROMISE(); + contacts_manager_->delete_revoked_dialog_invite_link(DialogId(request.chat_id_), request.invite_link_, + std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::deleteAllRevokedChatInviteLinks &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + contacts_manager_->delete_all_revoked_dialog_invite_links(DialogId(request.chat_id_), + UserId(request.creator_user_id_), std::move(promise)); } void Td::on_request(uint64 id, td_api::checkChatInviteLink &request) { @@ -6592,6 +6632,33 @@ void Td::on_request(uint64 id, const td_api::deleteFile &request) { "td_api::deleteFile"); } +void Td::on_request(uint64 id, td_api::getMessageFileType &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.message_file_head_); + CREATE_REQUEST_PROMISE(); + messages_manager_->get_message_file_type(request.message_file_head_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getMessageImportConfirmationText &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(make_tl_object(result.move_as_ok())); + } + }); + messages_manager_->get_message_import_confirmation_text(DialogId(request.chat_id_), std::move(query_promise)); +} + +void Td::on_request(uint64 id, const td_api::importMessages &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->import_messages(DialogId(request.chat_id_), request.message_file_, request.attached_files_, + std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::blockMessageSenderFromReplies &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); @@ -6758,6 +6825,12 @@ void Td::on_request(uint64 id, const td_api::toggleSupergroupIsAllHistoryAvailab request.is_all_history_available_, std::move(promise)); } +void Td::on_request(uint64 id, const td_api::toggleSupergroupIsBroadcastGroup &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + contacts_manager_->convert_channel_to_gigagroup(ChannelId(request.supergroup_id_), std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::reportSupergroupSpam &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); @@ -6766,19 +6839,22 @@ void Td::on_request(uint64 id, const td_api::reportSupergroupSpam &request) { } void Td::on_request(uint64 id, td_api::getSupergroupMembers &request) { - CREATE_REQUEST(GetSupergroupMembersRequest, request.supergroup_id_, std::move(request.filter_), request.offset_, - request.limit_); -} - -void Td::on_request(uint64 id, const td_api::deleteSupergroup &request) { - CHECK_IS_USER(); - CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->delete_channel(ChannelId(request.supergroup_id_), std::move(promise)); + CREATE_REQUEST_PROMISE(); + auto query_promise = + PromiseCreator::lambda([promise = std::move(promise), td = this](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(result.ok().get_chat_members_object(td)); + } + }); + contacts_manager_->get_channel_participants(ChannelId(request.supergroup_id_), std::move(request.filter_), string(), + request.offset_, request.limit_, -1, false, std::move(query_promise)); } void Td::on_request(uint64 id, td_api::closeSecretChat &request) { CREATE_OK_REQUEST_PROMISE(); - send_closure(secret_chats_manager_, &SecretChatsManager::cancel_chat, SecretChatId(request.secret_chat_id_), + send_closure(secret_chats_manager_, &SecretChatsManager::cancel_chat, SecretChatId(request.secret_chat_id_), false, std::move(promise)); } @@ -6987,15 +7063,30 @@ void Td::on_request(uint64 id, const td_api::removeChatActionBar &request) { void Td::on_request(uint64 id, td_api::reportChat &request) { CHECK_IS_USER(); + auto r_report_reason = ReportReason::get_report_reason(std::move(request.reason_), std::move(request.text_)); + if (r_report_reason.is_error()) { + return send_error_raw(id, r_report_reason.error().code(), r_report_reason.error().message()); + } CREATE_OK_REQUEST_PROMISE(); - messages_manager_->report_dialog(DialogId(request.chat_id_), request.reason_, - MessagesManager::get_message_ids(request.message_ids_), std::move(promise)); + messages_manager_->report_dialog(DialogId(request.chat_id_), MessagesManager::get_message_ids(request.message_ids_), + r_report_reason.move_as_ok(), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::reportChatPhoto &request) { + CHECK_IS_USER(); + auto r_report_reason = ReportReason::get_report_reason(std::move(request.reason_), std::move(request.text_)); + if (r_report_reason.is_error()) { + return send_error_raw(id, r_report_reason.error().code(), r_report_reason.error().message()); + } + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->report_dialog_photo(DialogId(request.chat_id_), FileId(request.file_id_, 0), + r_report_reason.move_as_ok(), std::move(promise)); } void Td::on_request(uint64 id, td_api::getChatStatisticsUrl &request) { CHECK_IS_USER(); - CREATE_REQUEST_PROMISE(); CLEAN_INPUT_STRING(request.parameters_); + CREATE_REQUEST_PROMISE(); messages_manager_->get_dialog_statistics_url(DialogId(request.chat_id_), request.parameters_, request.is_dark_, std::move(promise)); } @@ -7015,8 +7106,8 @@ void Td::on_request(uint64 id, const td_api::getMessageStatistics &request) { void Td::on_request(uint64 id, td_api::getStatisticalGraph &request) { CHECK_IS_USER(); - CREATE_REQUEST_PROMISE(); CLEAN_INPUT_STRING(request.token_); + CREATE_REQUEST_PROMISE(); contacts_manager_->load_statistics_graph(DialogId(request.chat_id_), request.token_, request.x_, std::move(promise)); } @@ -7584,8 +7675,7 @@ void Td::on_request(uint64 id, td_api::stopPoll &request) { void Td::on_request(uint64 id, const td_api::hideSuggestedAction &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - send_closure_later(config_manager_, &ConfigManager::dismiss_suggested_action, get_suggested_action(request.action_), - std::move(promise)); + contacts_manager_->dismiss_suggested_action(SuggestedAction(request.action_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getLoginUrlInfo &request) { @@ -7730,11 +7820,11 @@ void Td::on_request(uint64 id, td_api::getAllPassportElements &request) { void Td::on_request(uint64 id, td_api::setPassportElement &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.password_); - CREATE_REQUEST_PROMISE(); auto r_secure_value = get_secure_value(file_manager_.get(), std::move(request.element_)); if (r_secure_value.is_error()) { - return promise.set_error(r_secure_value.move_as_error()); + return send_error_raw(id, 400, r_secure_value.error().message()); } + CREATE_REQUEST_PROMISE(); send_closure(secure_manager_, &SecureManager::set_secure_value, std::move(request.password_), r_secure_value.move_as_ok(), std::move(promise)); } @@ -8085,7 +8175,7 @@ void Td::on_request(uint64 id, const td_api::getProxyLink &request) { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { - promise.set_value(make_tl_object(result.move_as_ok())); + promise.set_value(make_tl_object(result.move_as_ok())); } }); send_closure(G()->connection_creator(), &ConnectionCreator::get_proxy_link, request.proxy_id_, diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 30e422559..034475a86 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -134,6 +134,8 @@ class Td final : public NetQueryCallback { bool is_online() const; + void set_is_bot_online(bool is_bot_online); + template ActorId create_net_actor(ArgsT &&... args) { auto slot_id = request_actors_.create(ActorOwn<>(), RequestActorIdType); @@ -247,7 +249,7 @@ class Td final : public NetQueryCallback { static td_api::object_ptr static_request(td_api::object_ptr function); private: - static constexpr const char *TDLIB_VERSION = "1.7.0"; + static constexpr const char *TDLIB_VERSION = "1.7.2"; static constexpr int64 ONLINE_ALARM_ID = 0; static constexpr int64 PING_SERVER_ALARM_ID = -1; static constexpr int32 PING_SERVER_TIMEOUT = 300; @@ -299,6 +301,7 @@ class Td final : public NetQueryCallback { Container> request_actors_; bool is_online_ = false; + bool is_bot_online_ = false; NetQueryRef update_status_query_; int64 alarm_id_ = 1; @@ -590,10 +593,14 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::openMessageContent &request); + void on_request(uint64 id, td_api::getExternalLink &request); + void on_request(uint64 id, const td_api::getChatHistory &request); void on_request(uint64 id, const td_api::deleteChatHistory &request); + void on_request(uint64 id, const td_api::deleteChat &request); + void on_request(uint64 id, const td_api::getMessageThreadHistory &request); void on_request(uint64 id, td_api::searchChatMessages &request); @@ -604,6 +611,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, td_api::searchCallMessages &request); + void on_request(uint64 id, const td_api::deleteAllCallMessages &request); + void on_request(uint64 id, const td_api::searchChatRecentLocationMessages &request); void on_request(uint64 id, const td_api::getActiveLiveLocationMessages &request); @@ -634,8 +643,6 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, td_api::sendInlineQueryResultMessage &request); - void on_request(uint64 id, const td_api::sendChatSetTtlMessage &request); - void on_request(uint64 id, td_api::addLocalMessage &request); void on_request(uint64 id, td_api::editMessageText &request); @@ -722,6 +729,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::toggleGroupCallParticipantIsMuted &request); + void on_request(uint64 id, const td_api::setGroupCallParticipantVolumeLevel &request); + void on_request(uint64 id, const td_api::loadGroupCallParticipants &request); void on_request(uint64 id, const td_api::leaveGroupCall &request); @@ -750,6 +759,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::setChatPhoto &request); + void on_request(uint64 id, const td_api::setChatMessageTtlSetting &request); + void on_request(uint64 id, const td_api::setChatPermissions &request); void on_request(uint64 id, td_api::setChatDraftMessage &request); @@ -790,6 +801,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, td_api::setChatMemberStatus &request); + void on_request(uint64 id, const td_api::banChatMember &request); + void on_request(uint64 id, const td_api::canTransferOwnership &request); void on_request(uint64 id, td_api::transferChatOwnership &request); @@ -800,7 +813,25 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, td_api::getChatAdministrators &request); - void on_request(uint64 id, const td_api::generateChatInviteLink &request); + void on_request(uint64 id, const td_api::replacePrimaryChatInviteLink &request); + + void on_request(uint64 id, const td_api::createChatInviteLink &request); + + void on_request(uint64 id, td_api::editChatInviteLink &request); + + void on_request(uint64 id, td_api::getChatInviteLink &request); + + void on_request(uint64 id, const td_api::getChatInviteLinkCounts &request); + + void on_request(uint64 id, td_api::getChatInviteLinks &request); + + void on_request(uint64 id, td_api::getChatInviteLinkMembers &request); + + void on_request(uint64 id, td_api::revokeChatInviteLink &request); + + void on_request(uint64 id, td_api::deleteRevokedChatInviteLink &request); + + void on_request(uint64 id, const td_api::deleteAllRevokedChatInviteLinks &request); void on_request(uint64 id, td_api::checkChatInviteLink &request); @@ -830,6 +861,12 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::deleteFile &request); + void on_request(uint64 id, td_api::getMessageFileType &request); + + void on_request(uint64 id, const td_api::getMessageImportConfirmationText &request); + + void on_request(uint64 id, const td_api::importMessages &request); + void on_request(uint64 id, const td_api::blockMessageSenderFromReplies &request); void on_request(uint64 id, const td_api::getBlockedMessageSenders &request); @@ -878,12 +915,12 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::toggleSupergroupIsAllHistoryAvailable &request); + void on_request(uint64 id, const td_api::toggleSupergroupIsBroadcastGroup &request); + void on_request(uint64 id, const td_api::reportSupergroupSpam &request); void on_request(uint64 id, td_api::getSupergroupMembers &request); - void on_request(uint64 id, const td_api::deleteSupergroup &request); - void on_request(uint64 id, td_api::closeSecretChat &request); void on_request(uint64 id, td_api::getStickers &request); @@ -964,6 +1001,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, td_api::reportChat &request); + void on_request(uint64 id, td_api::reportChatPhoto &request); + void on_request(uint64 id, td_api::getChatStatisticsUrl &request); void on_request(uint64 id, const td_api::getChatStatistics &request); diff --git a/td/telegram/TdDb.cpp b/td/telegram/TdDb.cpp index 8b21c1f2c..f6795e146 100644 --- a/td/telegram/TdDb.cpp +++ b/td/telegram/TdDb.cpp @@ -116,6 +116,7 @@ Status init_binlog(Binlog &binlog, string path, BinlogKeyValue &binlog_p case LogEvent::HandlerType::ReadMessageThreadHistoryOnServer: case LogEvent::HandlerType::BlockMessageSenderFromRepliesOnServer: case LogEvent::HandlerType::UnpinAllDialogMessagesOnServer: + case LogEvent::HandlerType::DeleteAllCallMessagesFromServer: events.to_messages_manager.push_back(event.clone()); break; case LogEvent::HandlerType::AddMessagePushNotification: diff --git a/td/telegram/UpdatesManager.cpp b/td/telegram/UpdatesManager.cpp index 106380ad1..4a56771e6 100644 --- a/td/telegram/UpdatesManager.cpp +++ b/td/telegram/UpdatesManager.cpp @@ -20,6 +20,7 @@ #include "td/telegram/ContactsManager.h" #include "td/telegram/DialogAction.h" #include "td/telegram/DialogId.h" +#include "td/telegram/DialogInviteLink.h" #include "td/telegram/FolderId.h" #include "td/telegram/Global.h" #include "td/telegram/GroupCallManager.h" @@ -28,6 +29,7 @@ #include "td/telegram/Location.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessagesManager.h" +#include "td/telegram/MessageTtlSetting.h" #include "td/telegram/net/DcOptions.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/NotificationManager.h" @@ -50,14 +52,15 @@ #include "td/utils/algorithm.h" #include "td/utils/buffer.h" -#include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Random.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" #include "td/utils/StringBuilder.h" +#include "td/utils/Time.h" +#include #include namespace td { @@ -198,11 +201,22 @@ void UpdatesManager::fill_seq_gap(void *td) { } void UpdatesManager::fill_qts_gap(void *td) { - fill_gap(td, "qts"); + CHECK(td != nullptr); + if (G()->close_flag()) { + return; + } + + auto td_ptr = static_cast(td); + auto qts = std::numeric_limits::max(); + if (!td_ptr->updates_manager_->pending_qts_updates_.empty()) { + qts = td_ptr->updates_manager_->pending_qts_updates_.begin()->first; + } + string source = PSTRING() << "qts from " << td_ptr->updates_manager_->get_qts() << " to " << qts; + fill_gap(td, source.c_str()); } void UpdatesManager::fill_get_difference_gap(void *td) { - fill_gap(td, "getDifference"); + fill_gap(td, "rare getDifference calls"); } void UpdatesManager::fill_gap(void *td, const char *source) { @@ -315,13 +329,13 @@ void UpdatesManager::on_qts_ack(PtsManager::PtsId ack_token) { void UpdatesManager::save_pts(int32 pts) { if (pts == std::numeric_limits::max()) { G()->td_db()->get_binlog_pmc()->erase("updates.pts"); - } else if (!G()->ignore_backgrond_updates()) { + } else if (!G()->ignore_background_updates()) { G()->td_db()->get_binlog_pmc()->set("updates.pts", to_string(pts)); } } void UpdatesManager::save_qts(int32 qts) { - if (!G()->ignore_backgrond_updates()) { + if (!G()->ignore_background_updates()) { G()->td_db()->get_binlog_pmc()->set("updates.qts", to_string(qts)); } } @@ -343,7 +357,7 @@ Promise<> UpdatesManager::set_pts(int32 pts, const char *source) { } result = add_pts(pts); - if (last_get_difference_pts_ + FORCED_GET_DIFFERENCE_PTS_DIFF < get_pts()) { + if (last_get_difference_pts_ < get_pts() - FORCED_GET_DIFFERENCE_PTS_DIFF) { last_get_difference_pts_ = get_pts(); schedule_get_difference("set_pts"); } @@ -375,7 +389,7 @@ void UpdatesManager::set_date(int32 date, bool from_update, string date_source) date_ = date; date_source_ = std::move(date_source); - if (!G()->ignore_backgrond_updates()) { + if (!G()->ignore_background_updates()) { G()->td_db()->get_binlog_pmc()->set("updates.date", to_string(date)); } } else if (date < date_) { @@ -607,6 +621,7 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_ case telegram_api::messageActionSecureValuesSentMe::ID: case telegram_api::messageActionContactSignUp::ID: case telegram_api::messageActionGroupCall::ID: + case telegram_api::messageActionSetMessagesTTL::ID: break; case telegram_api::messageActionChatCreate::ID: { auto chat_create = static_cast(action); @@ -788,7 +803,8 @@ void UpdatesManager::on_get_updates(tl_object_ptr &&updat false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, update->id_, make_tl_object(from_id), make_tl_object(update->user_id_), std::move(update->fwd_from_), update->via_bot_id_, std::move(update->reply_to_), update->date_, - update->message_, nullptr, nullptr, std::move(update->entities_), 0, 0, nullptr, 0, string(), 0, Auto()); + update->message_, nullptr, nullptr, std::move(update->entities_), 0, 0, nullptr, 0, string(), 0, Auto(), + update->ttl_period_); on_pending_update( make_tl_object(std::move(message), update->pts_, update->pts_count_), 0, std::move(promise), "telegram_api::updatesShortMessage"); @@ -812,7 +828,7 @@ void UpdatesManager::on_get_updates(tl_object_ptr &&updat make_tl_object(update->from_id_), make_tl_object(update->chat_id_), std::move(update->fwd_from_), update->via_bot_id_, std::move(update->reply_to_), update->date_, update->message_, nullptr, nullptr, std::move(update->entities_), - 0, 0, nullptr, 0, string(), 0, Auto()); + 0, 0, nullptr, 0, string(), 0, Auto(), update->ttl_period_); on_pending_update( make_tl_object(std::move(message), update->pts_, update->pts_count_), 0, std::move(promise), "telegram_api::updatesShortChatMessage"); @@ -1110,7 +1126,7 @@ void UpdatesManager::init_state() { } auto pmc = G()->td_db()->get_binlog_pmc(); - if (G()->ignore_backgrond_updates()) { + if (G()->ignore_background_updates()) { // just in case pmc->erase("updates.pts"); pmc->erase("updates.qts"); @@ -1195,6 +1211,10 @@ void UpdatesManager::process_get_difference_updates( CHECK(!running_get_difference_); } + if (constructor_id == telegram_api::updateChat::ID) { + update = nullptr; + } + if (constructor_id == telegram_api::updateChannel::ID) { update = nullptr; } @@ -1310,9 +1330,9 @@ void UpdatesManager::on_get_difference(tl_object_ptrintermediate_state_); if (get_pts() != std::numeric_limits::max() && state->date_ == get_date() && (state->pts_ == get_pts() || - (min_postponed_update_pts_ != 0 && state->pts_ >= min_postponed_update_pts_ + 1000)) && + (min_postponed_update_pts_ != 0 && state->pts_ - 1000 >= min_postponed_update_pts_)) && (state->qts_ == get_qts() || - (min_postponed_update_qts_ != 0 && state->qts_ >= min_postponed_update_qts_ + 1000))) { + (min_postponed_update_qts_ != 0 && state->qts_ - 1000 >= min_postponed_update_qts_))) { on_get_updates_state(std::move(state), "get difference final slice"); VLOG(get_difference) << "Trying to switch back from getDifference to update processing"; break; @@ -1639,7 +1659,7 @@ void UpdatesManager::add_pending_qts_update(tl_object_ptr int32 old_qts = get_qts(); LOG(INFO) << "Process update with qts = " << qts << ", current qts = " << old_qts; - if (qts < old_qts - 1000001) { + if (qts < old_qts - 100001) { LOG(WARNING) << "Restore qts after qts overflow from " << old_qts << " to " << qts << " by " << oneline(to_string(update)); add_qts(qts - 1).set_value(Unit()); @@ -1656,7 +1676,7 @@ void UpdatesManager::add_pending_qts_update(tl_object_ptr CHECK(!running_get_difference_); - if (qts > old_qts + 1) { + if (qts - 1 > old_qts && old_qts > 0) { LOG(INFO) << "Postpone update with qts = " << qts; if (pending_qts_updates_.empty()) { set_qts_gap_timeout(MAX_UNFILLED_GAP_TIME); @@ -1842,7 +1862,7 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr } } - if (new_pts <= old_pts || (old_pts >= 1 && new_pts > old_pts + 500000000)) { + if (new_pts <= old_pts || (old_pts >= 1 && new_pts - 500000000 > old_pts)) { td_->messages_manager_->skip_old_pending_pts_update(std::move(update), new_pts, old_pts, pts_count, source); return promise.set_value(Unit()); } @@ -1856,7 +1876,7 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr return; } - if (old_pts + pts_count > new_pts) { + if (old_pts > new_pts - pts_count) { LOG(WARNING) << "Have old_pts (= " << old_pts << ") + pts_count (= " << pts_count << ") > new_pts (= " << new_pts << "). Logged in " << G()->shared_config().get_option_integer("authorization_date") << ". Update from " << source << " = " << oneline(to_string(update)); @@ -1870,7 +1890,7 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr accumulated_pts_ = new_pts; } - if (old_pts + accumulated_pts_count_ > accumulated_pts_) { + if (old_pts > accumulated_pts_ - accumulated_pts_count_) { LOG(WARNING) << "Have old_pts (= " << old_pts << ") + accumulated_pts_count (= " << accumulated_pts_count_ << ") > accumulated_pts (= " << accumulated_pts_ << "). new_pts = " << new_pts << ", pts_count = " << pts_count << ". Logged in " @@ -1883,7 +1903,7 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr LOG_IF(INFO, pts_count == 0 && update->get_id() != dummyUpdate::ID) << "Skip useless update " << to_string(update); - if (pending_pts_updates_.empty() && old_pts + accumulated_pts_count_ == accumulated_pts_ && + if (pending_pts_updates_.empty() && old_pts == accumulated_pts_ - accumulated_pts_count_ && !pts_gap_timeout_.has_timeout()) { if (pts_count > 0) { td_->messages_manager_->process_pts_update(std::move(update)); @@ -1899,15 +1919,14 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr pending_pts_updates_.emplace(new_pts, PendingPtsUpdate(std::move(update), new_pts, pts_count, std::move(promise))); - if (old_pts + accumulated_pts_count_ < accumulated_pts_) { + if (old_pts < accumulated_pts_ - accumulated_pts_count_) { set_pts_gap_timeout(MAX_UNFILLED_GAP_TIME); + last_pts_gap_time_ = Time::now(); return; } - CHECK(old_pts + accumulated_pts_count_ == accumulated_pts_); - if (!pending_pts_updates_.empty()) { - process_pending_pts_updates(); - } + CHECK(old_pts == accumulated_pts_ - accumulated_pts_count_); + process_pending_pts_updates(); } void UpdatesManager::postpone_pts_update(tl_object_ptr &&update, int32 pts, int32 pts_count, @@ -1937,9 +1956,11 @@ void UpdatesManager::process_seq_updates(int32 seq_end, int32 date, void UpdatesManager::process_qts_update(tl_object_ptr &&update_ptr, int32 qts, Promise &&promise) { LOG(DEBUG) << "Process " << to_string(update_ptr); - if (last_get_difference_qts_ + FORCED_GET_DIFFERENCE_PTS_DIFF < qts) { + if (last_get_difference_qts_ < qts - FORCED_GET_DIFFERENCE_PTS_DIFF) { + if (last_get_difference_qts_ != 0) { + schedule_get_difference("process_qts_update"); + } last_get_difference_qts_ = qts; - schedule_get_difference("process_qts_update"); } switch (update_ptr->get_id()) { case telegram_api::updateNewEncryptedMessage::ID: { @@ -1948,11 +1969,29 @@ void UpdatesManager::process_qts_update(tl_object_ptr &&up add_qts(qts)); break; } + case telegram_api::updateBotStopped::ID: { + auto update = move_tl_object_as(update_ptr); + td_->contacts_manager_->on_update_bot_stopped(UserId(update->user_id_), update->date_, + std::move(update->stopped_)); + add_qts(qts).set_value(Unit()); + break; + } + case telegram_api::updateChatParticipant::ID: { + auto update = move_tl_object_as(update_ptr); + td_->contacts_manager_->on_update_chat_participant(ChatId(update->chat_id_), UserId(update->actor_id_), + update->date_, DialogInviteLink(std::move(update->invite_)), + std::move(update->prev_participant_), + std::move(update->new_participant_)); + add_qts(qts).set_value(Unit()); + break; + } case telegram_api::updateChannelParticipant::ID: { auto update = move_tl_object_as(update_ptr); - td_->contacts_manager_->on_update_channel_participant(ChannelId(update->channel_id_), UserId(update->user_id_), - update->date_, std::move(update->prev_participant_), + td_->contacts_manager_->on_update_channel_participant(ChannelId(update->channel_id_), UserId(update->actor_id_), + update->date_, DialogInviteLink(std::move(update->invite_)), + std::move(update->prev_participant_), std::move(update->new_participant_)); + add_qts(qts).set_value(Unit()); break; } default: @@ -1968,8 +2007,17 @@ void UpdatesManager::process_pending_pts_updates() { update.second.promise.set_value(Unit()); } - set_pts(accumulated_pts_, "process pending updates") - .set_value(Unit()); // TODO can't set until get messages really stored on persistent storage + if (last_pts_gap_time_ != 0) { + auto diff = Time::now() - last_pts_gap_time_; + last_pts_gap_time_ = 0; + if (diff > 0.1) { + VLOG(get_difference) << "Gap in pts from " << accumulated_pts_ - accumulated_pts_count_ << " to " + << accumulated_pts_ << " has been filled in " << diff << " seconds"; + } + } + + set_pts(accumulated_pts_, "postpone_pending_pts_update") + .set_value(Unit()); // TODO can't set until updates are stored on persistent storage drop_pending_pts_updates(); } @@ -2284,6 +2332,15 @@ void UpdatesManager::on_update(tl_object_ptr u promise.set_value(Unit()); } +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + MessageTtlSetting message_ttl_setting; + if ((update->flags_ & telegram_api::updatePeerHistoryTTL::TTL_PERIOD_MASK) != 0) { + message_ttl_setting = MessageTtlSetting(update->ttl_period_); + } + td_->messages_manager_->on_update_dialog_message_ttl_setting(DialogId(update->peer_), message_ttl_setting); + promise.set_value(Unit()); +} + void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { td_->contacts_manager_->on_update_peer_located(std::move(update->peers_), true); promise.set_value(Unit()); @@ -2429,6 +2486,8 @@ int32 UpdatesManager::get_update_pts(const telegram_api::Update *update) { bool UpdatesManager::is_qts_update(const telegram_api::Update *update) { switch (update->get_id()) { case telegram_api::updateNewEncryptedMessage::ID: + case telegram_api::updateBotStopped::ID: + case telegram_api::updateChatParticipant::ID: case telegram_api::updateChannelParticipant::ID: return true; default: @@ -2440,6 +2499,10 @@ int32 UpdatesManager::get_update_qts(const telegram_api::Update *update) { switch (update->get_id()) { case telegram_api::updateNewEncryptedMessage::ID: return static_cast(update)->qts_; + case telegram_api::updateBotStopped::ID: + return static_cast(update)->qts_; + case telegram_api::updateChatParticipant::ID: + return static_cast(update)->qts_; case telegram_api::updateChannelParticipant::ID: return static_cast(update)->qts_; default: @@ -2826,6 +2889,16 @@ void UpdatesManager::on_update(tl_object_ptr upd promise.set_value(Unit()); } +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + auto qts = update->qts_; + add_pending_qts_update(std::move(update), qts, std::move(promise)); +} + +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + auto qts = update->qts_; + add_pending_qts_update(std::move(update), qts, std::move(promise)); +} + void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { auto qts = update->qts_; add_pending_qts_update(std::move(update), qts, std::move(promise)); diff --git a/td/telegram/UpdatesManager.h b/td/telegram/UpdatesManager.h index 810f37472..d13329988 100644 --- a/td/telegram/UpdatesManager.h +++ b/td/telegram/UpdatesManager.h @@ -21,6 +21,7 @@ #include "td/utils/common.h" #include "td/utils/logging.h" +#include "td/utils/Status.h" #include "td/utils/tl_storers.h" #include @@ -58,9 +59,10 @@ class updateSentMessage : public telegram_api::Update { int64 random_id_; MessageId message_id_; int32 date_; + int32 ttl_period_; - updateSentMessage(int64 random_id, MessageId message_id, int32 date) - : random_id_(random_id), message_id_(message_id), date_(date) { + updateSentMessage(int64 random_id, MessageId message_id, int32 date, int32 ttl_period) + : random_id_(random_id), message_id_(message_id), date_(date), ttl_period_(ttl_period) { } static constexpr int32 ID = 1234567890; @@ -81,6 +83,7 @@ class updateSentMessage : public telegram_api::Update { s.store_field("random_id", random_id_); s.store_field("message_id", message_id_.get()); s.store_field("date", date_); + s.store_field("ttl_period", ttl_period_); s.store_class_end(); } }; @@ -170,6 +173,7 @@ class UpdatesManager : public Actor { int32 accumulated_pts_count_ = 0; int32 accumulated_pts_ = -1; double last_pts_jump_warning_time_ = 0; + double last_pts_gap_time_ = 0; std::multimap pending_pts_updates_; std::multimap postponed_pts_updates_; @@ -333,6 +337,8 @@ class UpdatesManager : public Actor { void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); @@ -446,6 +452,8 @@ class UpdatesManager : public Actor { void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); // unsupported updates diff --git a/td/telegram/Version.h b/td/telegram/Version.h index afaed1585..11d98bf11 100644 --- a/td/telegram/Version.h +++ b/td/telegram/Version.h @@ -8,7 +8,7 @@ namespace td { -constexpr int32 MTPROTO_LAYER = 122; +constexpr int32 MTPROTO_LAYER = 124; enum class Version : int32 { Initial, // 0 diff --git a/td/telegram/WebPageBlock.cpp b/td/telegram/WebPageBlock.cpp index 2f94325fa..7ab72c171 100644 --- a/td/telegram/WebPageBlock.cpp +++ b/td/telegram/WebPageBlock.cpp @@ -1851,7 +1851,7 @@ RichText get_rich_text(tl_object_ptr &&rich_text_ptr, if (it != documents.end()) { result.type = RichText::Type::Icon; result.document_file_id = it->second; - Dimensions dimensions = get_dimensions(rich_text->w_, rich_text->h_); + Dimensions dimensions = get_dimensions(rich_text->w_, rich_text->h_, "textImage"); result.content = PSTRING() << (dimensions.width * static_cast(65536) + dimensions.height); } else { LOG(ERROR) << "Can't find document " << rich_text->document_id_; @@ -2070,7 +2070,7 @@ unique_ptr get_web_page_block(Td *td, tl_object_ptrw_, page_block->h_); + dimensions = get_dimensions(page_block->w_, page_block->h_, "pageBlockEmbed"); } return td::make_unique( std::move(page_block->url_), std::move(page_block->html_), std::move(poster_photo), dimensions, @@ -2221,7 +2221,7 @@ unique_ptr get_web_page_block(Td *td, tl_object_ptr(page_block_ptr); Location location(std::move(page_block->geo_)); auto zoom = page_block->zoom_; - Dimensions dimensions = get_dimensions(page_block->w_, page_block->h_); + Dimensions dimensions = get_dimensions(page_block->w_, page_block->h_, "pageBlockMap"); if (location.empty()) { LOG(ERROR) << "Receive invalid map location"; break; diff --git a/td/telegram/WebPagesManager.cpp b/td/telegram/WebPagesManager.cpp index 4ec2c9743..ca6a5024f 100644 --- a/td/telegram/WebPagesManager.cpp +++ b/td/telegram/WebPagesManager.cpp @@ -489,7 +489,7 @@ WebPageId WebPagesManager::on_get_web_page(tl_object_ptr page->embed_type = std::move(web_page->embed_type_); } if (web_page->flags_ & WEBPAGE_FLAG_HAS_EMBEDDED_PREVIEW_SIZE) { - page->embed_dimensions = get_dimensions(web_page->embed_width_, web_page->embed_height_); + page->embed_dimensions = get_dimensions(web_page->embed_width_, web_page->embed_height_, "webPage"); } if (web_page->flags_ & WEBPAGE_FLAG_HAS_DURATION) { page->duration = web_page->duration_; diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index bd231018a..fb9b58a0e 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -24,6 +24,7 @@ #include "td/utils/crypto.h" #include "td/utils/ExitGuard.h" #include "td/utils/FileLog.h" +#include "td/utils/filesystem.h" #include "td/utils/format.h" #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" @@ -41,6 +42,7 @@ #include "td/utils/Status.h" #include "td/utils/StringBuilder.h" #include "td/utils/Time.h" +#include "td/utils/utf8.h" #ifndef USE_READLINE #include "td/utils/find_boundary.h" @@ -453,12 +455,18 @@ class CliClient final : public Actor { int64 as_chat_id(Slice str) const { str = trim(str); + if (str == "me") { + return my_id_; + } if (str[0] == '@') { - auto it = username_to_user_id_.find(to_lower(str.substr(1))); + str.remove_prefix(1); + } + if (is_alpha(str[0])) { + auto it = username_to_user_id_.find(to_lower(str)); if (it != username_to_user_id_.end()) { return it->second; } - auto it2 = username_to_supergroup_id_.find(to_lower(str.substr(1))); + auto it2 = username_to_supergroup_id_.find(to_lower(str)); if (it2 != username_to_supergroup_id_.end()) { auto supergroup_id = it2->second; return static_cast(-1000'000'000'000ll) - supergroup_id; @@ -466,9 +474,6 @@ class CliClient final : public Actor { LOG(ERROR) << "Can't resolve " << str; return 0; } - if (str == "me") { - return my_id_; - } return to_integer(str); } @@ -534,17 +539,20 @@ class CliClient final : public Actor { int32 as_user_id(Slice str) const { str = trim(str); + if (str == "me") { + return my_id_; + } if (str[0] == '@') { - auto it = username_to_user_id_.find(to_lower(str.substr(1))); + str.remove_prefix(1); + } + if (is_alpha(str[0])) { + auto it = username_to_user_id_.find(to_lower(str)); if (it != username_to_user_id_.end()) { return it->second; } LOG(ERROR) << "Can't find user " << str; return 0; } - if (str == "me") { - return my_id_; - } return to_integer(str); } @@ -561,10 +569,17 @@ class CliClient final : public Actor { return result; } - int32 as_supergroup_id(Slice str) { + int32 as_supergroup_id(Slice str) const { str = trim(str); if (str[0] == '@') { - return username_to_supergroup_id_[to_lower(str.substr(1))]; + str.remove_prefix(1); + } + if (is_alpha(str[0])) { + auto it = username_to_supergroup_id_.find(to_lower(str)); + if (it == username_to_supergroup_id_.end()) { + return 0; + } + return it->second; } auto result = to_integer(str); int64 shift = static_cast(-1000000000000ll); @@ -820,6 +835,17 @@ class CliClient final : public Actor { } break; } + case td_api::updateNewMessage::ID: { + auto message = static_cast(result.get())->message_.get(); + if (message != nullptr && message->content_->get_id() == td_api::messageText::ID) { + auto chat_id = message->chat_id_; + auto text = static_cast(message->content_.get())->text_->text_; + if (text == "/start" && use_test_dc_) { + on_cmd(PSTRING() << "sm " << chat_id << " Hi!"); + } + } + break; + } case td_api::file::ID: on_get_file(*static_cast(result.get())); break; @@ -1316,6 +1342,9 @@ class CliClient final : public Actor { static td_api::object_ptr get_chat_report_reason(MutableSlice reason) { reason = trim(reason); + if (reason == "null") { + return nullptr; + } if (reason == "spam") { return td_api::make_object(); } @@ -1334,7 +1363,10 @@ class CliClient final : public Actor { if (reason == "geo" || reason == "location") { return td_api::make_object(); } - return td_api::make_object(reason.str()); + if (reason == "fake") { + return td_api::make_object(); + } + return td_api::make_object(); } static td_api::object_ptr get_network_type(MutableSlice type) { @@ -1358,13 +1390,19 @@ class CliClient final : public Actor { return nullptr; } - static td_api::object_ptr as_suggested_action(Slice action) { + td_api::object_ptr as_suggested_action(Slice action) const { if (action == "unarchive") { return td_api::make_object(); } if (action == "number") { return td_api::make_object(); } + if (action == "ticks") { + return td_api::make_object(); + } + if (begins_with(action, "giga")) { + return td_api::make_object(as_supergroup_id(action.substr(4))); + } return nullptr; } @@ -2011,6 +2049,9 @@ class CliClient final : public Actor { get_args(args, limit, offset_message_id, only_missed); send_request(td_api::make_object(as_message_id(offset_message_id), as_limit(limit), only_missed)); + } else if (op == "DeleteAllCallMessages") { + bool revoke = as_bool(args); + send_request(td_api::make_object(revoke)); } else if (op == "SCRLM") { string chat_id; string limit; @@ -2257,7 +2298,7 @@ class CliClient final : public Actor { } else if (op == "gatss") { send_request(td_api::make_object(as_file_id(args))); } else if (op == "storage") { - send_request(td_api::make_object(as_limit(args))); + send_request(td_api::make_object(to_integer(args))); } else if (op == "storage_fast") { send_request(td_api::make_object()); } else if (op == "database") { @@ -2536,7 +2577,8 @@ class CliClient final : public Actor { int32 max_file_id = as_file_id(file_id); int32 min_file_id = (op == "dff" ? 1 : max_file_id); for (int32 i = min_file_id; i <= max_file_id; i++) { - send_request(td_api::make_object(i, priority, offset, as_limit(limit), op == "dfs")); + send_request( + td_api::make_object(i, priority, offset, to_integer(limit), op == "dfs")); } } else if (op == "cdf") { send_request(td_api::make_object(as_file_id(args), false)); @@ -2592,11 +2634,6 @@ class CliClient final : public Actor { send_request(td_api::make_object(as_user_id(args))); } else if (op == "scstn") { send_request(td_api::make_object(as_chat_id(args))); - } else if (op == "sscttl" || op == "setSecretChatTtl") { - string chat_id; - int32 ttl; - get_args(args, chat_id, ttl); - send_request(td_api::make_object(as_chat_id(chat_id), ttl)); } else if (op == "closeSC" || op == "cancelSC") { send_request(td_api::make_object(as_secret_chat_id(args))); } else if (op == "cc" || op == "CreateCall") { @@ -2668,6 +2705,13 @@ class CliClient final : public Actor { get_args(args, group_call_id, user_id, is_muted); send_request(td_api::make_object(as_group_call_id(group_call_id), as_user_id(user_id), is_muted)); + } else if (op == "sgcpvl") { + string group_call_id; + string user_id; + int32 volume_level; + get_args(args, group_call_id, user_id, volume_level); + send_request(td_api::make_object(as_group_call_id(group_call_id), + as_user_id(user_id), volume_level)); } else if (op == "lgcp") { string group_call_id; string limit; @@ -2678,8 +2722,67 @@ class CliClient final : public Actor { send_request(td_api::make_object(as_group_call_id(args))); } else if (op == "dgc") { send_request(td_api::make_object(as_group_call_id(args))); + } else if (op == "rpcil") { + string chat_id = args; + send_request(td_api::make_object(as_chat_id(chat_id))); + } else if (op == "ccilt") { + string chat_id; + int32 expire_date; + int32 member_limit; + get_args(args, chat_id, expire_date, member_limit); + send_request(td_api::make_object(as_chat_id(chat_id), expire_date, member_limit)); + } else if (op == "ecil") { + string chat_id; + string invite_link; + int32 expire_date; + int32 member_limit; + get_args(args, chat_id, invite_link, expire_date, member_limit); + send_request( + td_api::make_object(as_chat_id(chat_id), invite_link, expire_date, member_limit)); + } else if (op == "rcil") { + string chat_id; + string invite_link; + get_args(args, chat_id, invite_link); + send_request(td_api::make_object(as_chat_id(chat_id), invite_link)); + } else if (op == "gcilc") { + string chat_id = args; + send_request(td_api::make_object(as_chat_id(chat_id))); } else if (op == "gcil") { - send_request(td_api::make_object(as_chat_id(args))); + string chat_id; + string invite_link; + get_args(args, chat_id, invite_link); + send_request(td_api::make_object(as_chat_id(chat_id), invite_link)); + } else if (op == "gcils" || op == "gcilr") { + string chat_id; + string creator_user_id; + int32 offset_date; + string offset_invite_link; + string limit; + get_args(args, chat_id, creator_user_id, offset_date, offset_invite_link, limit); + send_request(td_api::make_object(as_chat_id(chat_id), as_user_id(creator_user_id), + op == "gcilr", offset_date, offset_invite_link, + as_limit(limit))); + } else if (op == "gcilm") { + string chat_id; + string invite_link; + string offset_user_id; + int32 offset_date; + string limit; + get_args(args, chat_id, invite_link, offset_user_id, offset_date, limit); + send_request(td_api::make_object( + as_chat_id(chat_id), invite_link, + td_api::make_object(as_user_id(offset_user_id), offset_date), as_limit(limit))); + } else if (op == "drcil") { + string chat_id; + string invite_link; + get_args(args, chat_id, invite_link); + send_request(td_api::make_object(as_chat_id(chat_id), invite_link)); + } else if (op == "darcil") { + string chat_id; + string creator_user_id; + get_args(args, chat_id, creator_user_id); + send_request(td_api::make_object(as_chat_id(chat_id), + as_user_id(creator_user_id))); } else if (op == "ccil") { send_request(td_api::make_object(args)); } else if (op == "jcbil") { @@ -2796,10 +2899,10 @@ class CliClient final : public Actor { get_args(args, chat_id, is_marked_as_read); send_request(td_api::make_object(as_chat_id(chat_id), is_marked_as_read)); } else if (op == "tmsib") { - string chat_id; + string sender_id; bool is_blocked; - get_args(args, chat_id, is_blocked); - send_request(td_api::make_object(as_message_sender(chat_id), is_blocked)); + get_args(args, sender_id, is_blocked); + send_request(td_api::make_object(as_message_sender(sender_id), is_blocked)); } else if (op == "bmsfr") { string message_id; bool delete_message; @@ -2907,6 +3010,30 @@ class CliClient final : public Actor { as_input_file(document), nullptr, true, as_caption("")); return content; }))); + } else if (op == "gmft") { + auto r_message_file_head = read_file_str(args, 2 << 10); + if (r_message_file_head.is_error()) { + LOG(ERROR) << r_message_file_head.error(); + } else { + auto message_file_head = r_message_file_head.move_as_ok(); + while (!check_utf8(message_file_head)) { + message_file_head.pop_back(); + } + send_request(td_api::make_object(message_file_head)); + } + } else if (op == "gmict") { + string chat_id; + get_args(args, chat_id); + send_request(td_api::make_object(as_chat_id(chat_id))); + } else if (op == "im") { + string chat_id; + string message_file; + vector attached_files; + get_args(args, chat_id, message_file, args); + attached_files = full_split(args); + send_request(td_api::make_object( + as_chat_id(chat_id), as_input_file(message_file), + transform(attached_files, [](const string &attached_file) { return as_input_file(attached_file); }))); } else if (op == "em") { string chat_id; string message_id; @@ -3351,18 +3478,20 @@ class CliClient final : public Actor { string title; get_args(args, user_ids_string, title); send_request(td_api::make_object(as_user_ids(user_ids_string), title)); - } else if (op == "cnch") { - send_request(td_api::make_object(args, true, "Description", nullptr)); - } else if (op == "cnsg") { - send_request(td_api::make_object(args, false, "Description", nullptr)); - } else if (op == "cngc") { + } else if (op == "cnchc") { + send_request(td_api::make_object(args, true, "Description", nullptr, false)); + } else if (op == "cnsgc") { + send_request(td_api::make_object(args, false, "Description", nullptr, false)); + } else if (op == "cnsgcloc") { send_request(td_api::make_object( - args, false, "Description", - td_api::make_object(as_location("40.0", "60.0"), "address"))); + args, false, "Description", td_api::make_object(as_location("40.0", "60.0"), "address"), + false)); + } else if (op == "cnsgcimport") { + send_request(td_api::make_object(args, false, "Description", nullptr, true)); } else if (op == "UpgradeBasicGroupChatToSupergroupChat") { send_request(td_api::make_object(as_chat_id(args))); - } else if (op == "DeleteSupergroup") { - send_request(td_api::make_object(as_supergroup_id(args))); + } else if (op == "DeleteChat") { + send_request(td_api::make_object(as_chat_id(args))); } else if (op == "gcpc") { send_request(td_api::make_object()); } else if (op == "gcpcl") { @@ -3449,6 +3578,11 @@ class CliClient final : public Actor { send_request(td_api::make_object( as_chat_id(chat_id), td_api::make_object(as_input_file(animation), to_double(main_frame_timestamp)))); + } else if (op == "scmts") { + string chat_id; + int32 ttl; + get_args(args, chat_id, ttl); + send_request(td_api::make_object(as_chat_id(chat_id), ttl)); } else if (op == "scperm") { string chat_id; string permissions; @@ -3478,6 +3612,14 @@ class CliClient final : public Actor { string user_ids; get_args(args, chat_id, user_ids); send_request(td_api::make_object(as_chat_id(chat_id), as_user_ids(user_ids))); + } else if (op == "bcm") { + string chat_id; + string user_id; + int32 banned_until_date; + bool revoke_messages; + get_args(args, chat_id, user_id, banned_until_date, revoke_messages); + send_request(td_api::make_object(as_chat_id(chat_id), as_user_id(user_id), + banned_until_date, revoke_messages)); } else if (op == "spolla") { string chat_id; string message_id; @@ -3523,30 +3665,30 @@ class CliClient final : public Actor { status = td_api::make_object("", true, true); } else if (status_str == "uncreator") { status = td_api::make_object("", false, false); - } else if (status_str == "anon") { - status = td_api::make_object("anon", true, true, true, true, true, true, - true, true, true, false, true); } else if (status_str == "anonadmin") { - status = td_api::make_object("anon", false, false, false, false, false, - false, false, false, false, false, true); + status = td_api::make_object("anon", true, true, true, true, true, true, + true, true, true, true, true, true); + } else if (status_str == "anon") { + status = td_api::make_object( + "anon", false, false, false, false, false, false, false, false, false, false, false, true); } else if (status_str == "addadmin") { - status = td_api::make_object("anon", false, false, false, false, false, - false, false, false, true, false, false); + status = td_api::make_object( + "anon", false, false, false, false, false, false, false, false, false, true, false, false); } else if (status_str == "calladmin") { - status = td_api::make_object("anon", false, false, false, false, false, - false, false, false, false, true, false); + status = td_api::make_object( + "anon", false, false, false, false, false, false, false, false, false, false, true, false); } else if (status_str == "admin") { - status = td_api::make_object("", true, true, true, true, true, true, - true, true, true, false, false); + status = td_api::make_object("", true, false, true, true, true, true, + true, true, true, true, true, false); } else if (status_str == "adminq") { - status = td_api::make_object("title", true, true, true, true, true, true, - true, true, true, false, false); + status = td_api::make_object("title", true, false, true, true, true, + true, true, true, true, true, true, false); } else if (status_str == "minadmin") { status = td_api::make_object("", true, true, false, false, false, false, - false, false, false, false, false); + false, false, false, false, false, false); } else if (status_str == "unadmin") { status = td_api::make_object("", true, false, false, false, false, false, - false, false, false, false, false); + false, false, false, false, false, false); } else if (status_str == "rest") { status = td_api::make_object( true, static_cast(120 + std::time(nullptr)), @@ -3629,6 +3771,10 @@ class CliClient final : public Actor { get_args(args, supergroup_id, is_all_history_available); send_request(td_api::make_object(as_supergroup_id(supergroup_id), is_all_history_available)); + } else if (op == "ToggleSupergroupIsBroadcastGroup") { + string supergroup_id; + get_args(args, supergroup_id); + send_request(td_api::make_object(as_supergroup_id(supergroup_id))); } else if (op == "tsgsm") { string supergroup_id; bool sign_messages; @@ -3673,7 +3819,7 @@ class CliClient final : public Actor { send_request(td_api::make_object(as_chat_id(chat_id))); } else if (op == "grib") { send_request(td_api::make_object()); - } else if (op == "spc" || op == "su" || op == "sch") { + } else if (op == "spc" || op == "su") { send_request(td_api::make_object(args)); } else if (op == "spcs") { send_request(td_api::make_object(args)); @@ -3745,6 +3891,9 @@ class CliClient final : public Actor { string message_id; get_args(args, chat_id, message_id); send_request(td_api::make_object(as_chat_id(chat_id), as_message_id(message_id))); + } else if (op == "gel") { + string link = args; + send_request(td_api::make_object(link)); } else if (op == "racm") { string chat_id = args; send_request(td_api::make_object(as_chat_id(chat_id))); @@ -3801,11 +3950,20 @@ class CliClient final : public Actor { send_request(td_api::make_object(as_chat_id(chat_id))); } else if (op == "rc") { string chat_id; - string reason; string message_ids; - get_args(args, chat_id, reason, message_ids); - send_request(td_api::make_object(as_chat_id(chat_id), get_chat_report_reason(reason), - as_message_ids(message_ids))); + string reason; + string text; + get_args(args, chat_id, message_ids, reason, text); + send_request(td_api::make_object(as_chat_id(chat_id), as_message_ids(message_ids), + get_chat_report_reason(reason), text)); + } else if (op == "rcp") { + string chat_id; + string file_id; + string reason; + string text; + get_args(args, chat_id, file_id, reason, text); + send_request(td_api::make_object(as_chat_id(chat_id), as_file_id(file_id), + get_chat_report_reason(reason), text)); } else if (op == "gcsu") { string chat_id; string parameters; @@ -3846,7 +4004,7 @@ class CliClient final : public Actor { int64 x; get_args(args, chat_id, token, x); send_request(td_api::make_object(as_chat_id(chat_id), token, x)); - } else if (op == "hsa" || op == "glu" || op == "glua") { + } else if (op == "hsa") { send_request(td_api::make_object(as_suggested_action(args))); } else if (op == "glui" || op == "glu" || op == "glua") { string chat_id; diff --git a/td/telegram/files/FileDownloader.cpp b/td/telegram/files/FileDownloader.cpp index 7598c8ea5..d06b697db 100644 --- a/td/telegram/files/FileDownloader.cpp +++ b/td/telegram/files/FileDownloader.cpp @@ -367,7 +367,7 @@ Result FileDownloader::process_part(Part part, NetQueryPtr net_query) { bytes.as_slice()); } - auto slice = bytes.as_slice().truncate(part.size); + auto slice = bytes.as_slice().substr(0, part.size); TRY_STATUS(acquire_fd()); LOG(INFO) << "Got " << slice.size() << " bytes at offset " << part.offset << " for \"" << path_ << '"'; TRY_RESULT(written, fd_.pwrite(slice, part.offset)); diff --git a/td/telegram/files/FileManager.cpp b/td/telegram/files/FileManager.cpp index 6e30692f4..0849e3f28 100644 --- a/td/telegram/files/FileManager.cpp +++ b/td/telegram/files/FileManager.cpp @@ -466,7 +466,7 @@ void FileNode::on_info_flushed() { info_changed_flag_ = false; } -string FileNode::suggested_name() const { +string FileNode::suggested_path() const { if (!remote_name_.empty()) { return remote_name_; } @@ -685,8 +685,8 @@ const string &FileView::remote_name() const { return node_->remote_name_; } -string FileView::suggested_name() const { - return node_->suggested_name(); +string FileView::suggested_path() const { + return node_->suggested_path(); } DialogId FileView::owner_dialog_id() const { @@ -862,7 +862,7 @@ string FileManager::get_file_name(FileType file_type, Slice path) { break; case FileType::VoiceNote: if (extension != "ogg" && extension != "oga" && extension != "mp3" && extension != "mpeg3" && - extension != "m4a") { + extension != "m4a" && extension != "opus") { return fix_file_extension(file_name, "voice", "oga"); } break; @@ -1613,6 +1613,7 @@ Result FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy } node->need_load_from_pmc_ |= other_node->need_load_from_pmc_; node->can_search_locally_ &= other_node->can_search_locally_; + node->upload_prefer_small_ |= other_node->upload_prefer_small_; if (drop_last_successful_force_reupload_time) { node->last_successful_force_reupload_time_ = -1e10; @@ -2000,7 +2001,7 @@ bool FileManager::set_content(FileId file_id, BufferSlice bytes) { node->download_id_ = id; node->is_download_started_ = true; send_closure(file_load_manager_, &FileLoadManager::from_bytes, id, node->remote_.full.value().file_type_, - std::move(bytes), node->suggested_name()); + std::move(bytes), node->suggested_path()); return true; } @@ -2317,7 +2318,7 @@ void FileManager::run_download(FileNodePtr node, bool force_update_priority) { node->download_id_ = id; node->is_download_started_ = false; LOG(INFO) << "Run download of file " << file_id << " of size " << node->size_ << " from " - << node->remote_.full.value() << " with suggested name " << node->suggested_name() << " and encyption key " + << node->remote_.full.value() << " with suggested name " << node->suggested_path() << " and encyption key " << node->encryption_key_; auto download_offset = node->download_offset_; auto download_limit = node->download_limit_; @@ -2328,19 +2329,20 @@ void FileManager::run_download(FileNodePtr node, bool force_update_priority) { download_offset = 0; } send_closure(file_load_manager_, &FileLoadManager::download, id, node->remote_.full.value(), node->local_, - node->size_, node->suggested_name(), node->encryption_key_, node->can_search_locally_, download_offset, + node->size_, node->suggested_path(), node->encryption_key_, node->can_search_locally_, download_offset, download_limit, priority); } class FileManager::ForceUploadActor : public Actor { public: ForceUploadActor(FileManager *file_manager, FileId file_id, std::shared_ptr callback, - int32 new_priority, uint64 upload_order, ActorShared<> parent) + int32 new_priority, uint64 upload_order, bool prefer_small, ActorShared<> parent) : file_manager_(file_manager) , file_id_(file_id) , callback_(std::move(callback)) , new_priority_(new_priority) , upload_order_(upload_order) + , prefer_small_(prefer_small) , parent_(std::move(parent)) { } @@ -2350,9 +2352,11 @@ class FileManager::ForceUploadActor : public Actor { std::shared_ptr callback_; int32 new_priority_; uint64 upload_order_; + bool prefer_small_; ActorShared<> parent_; bool is_active_{false}; int attempt_{0}; + class UploadCallback : public FileManager::UploadCallback { public: explicit UploadCallback(ActorId callback) : callback_(std::move(callback)) { @@ -2449,7 +2453,7 @@ class FileManager::ForceUploadActor : public Actor { is_active_ = true; attempt_++; send_closure(G()->file_manager(), &FileManager::resume_upload, file_id_, std::vector(), create_callback(), - new_priority_, upload_order_, attempt_ == 2); + new_priority_, upload_order_, attempt_ == 2, prefer_small_); } void tear_down() override { @@ -2468,7 +2472,7 @@ void FileManager::on_force_reupload_success(FileId file_id) { } void FileManager::resume_upload(FileId file_id, std::vector bad_parts, std::shared_ptr callback, - int32 new_priority, uint64 upload_order, bool force) { + int32 new_priority, uint64 upload_order, bool force, bool prefer_small) { auto node = get_sync_file_node(file_id); if (!node) { LOG(INFO) << "File " << file_id << " not found"; @@ -2488,7 +2492,7 @@ void FileManager::resume_upload(FileId file_id, std::vector bad_parts, std: } create_actor("ForceUploadActor", this, file_id, std::move(callback), new_priority, upload_order, - context_->create_reference()) + prefer_small, context_->create_reference()) .release(); return; } @@ -2497,6 +2501,9 @@ void FileManager::resume_upload(FileId file_id, std::vector bad_parts, std: if (force) { node->remote_.is_full_alive = false; } + if (prefer_small) { + node->upload_prefer_small_ = true; + } if (node->upload_pause_ == file_id) { node->set_upload_pause(FileId()); } @@ -2676,7 +2683,7 @@ void FileManager::run_generate(FileNodePtr node) { QueryId id = queries_container_.create(Query{file_id, Query::Type::Generate}); node->generate_id_ = id; send_closure(file_generate_manager_, &FileGenerateManager::generate_file, id, *node->generate_, node->local_, - node->suggested_name(), [file_manager = this, id] { + node->suggested_path(), [file_manager = this, id] { class Callback : public FileGenerateCallback { ActorId actor_; uint64 query_id_; @@ -2806,10 +2813,15 @@ void FileManager::run_upload(FileNodePtr node, std::vector bad_parts) { auto new_priority = narrow_cast(bad_parts.empty() ? -priority : priority); td::remove_if(bad_parts, [](auto part_id) { return part_id < 0; }); + auto expected_size = file_view.expected_size(true); + if (node->upload_prefer_small_ && (10 << 20) < expected_size && expected_size < (30 << 20)) { + expected_size = 10 << 20; + } + QueryId id = queries_container_.create(Query{file_id, Query::Type::Upload}); node->upload_id_ = id; send_closure(file_load_manager_, &FileLoadManager::upload, id, node->local_, node->remote_.partial_or_empty(), - file_view.expected_size(true), node->encryption_key_, new_priority, std::move(bad_parts)); + expected_size, node->encryption_key_, new_priority, std::move(bad_parts)); LOG(INFO) << "File " << file_id << " upload request has sent to FileLoadManager"; } @@ -3179,6 +3191,70 @@ Result FileManager::get_map_thumbnail_file_id(Location location, int32 z FileLocationSource::FromUser, string(), std::move(conversion), owner_dialog_id, 0); } +FileType FileManager::guess_file_type(const tl_object_ptr &file) { + if (file == nullptr) { + return FileType::Temp; + } + + auto guess_file_type_by_path = [](const string &file_path) { + PathView path_view(file_path); + auto file_name = path_view.file_name(); + auto extension = path_view.extension(); + if (extension == "jpg" || extension == "jpeg") { + return FileType::Photo; + } + if (extension == "ogg" || extension == "oga" || extension == "opus") { + return FileType::VoiceNote; + } + if (extension == "3gp" || extension == "mov") { + return FileType::Video; + } + if (extension == "mp3" || extension == "mpeg3" || extension == "m4a") { + return FileType::Audio; + } + if (extension == "webp" || extension == "tgs") { + return FileType::Sticker; + } + if (extension == "gif") { + return FileType::Animation; + } + if (extension == "mp4" || extension == "mpeg4") { + return to_lower(file_name).find("-gif-") != string::npos ? FileType::Animation : FileType::Video; + } + return FileType::Document; + }; + + switch (file->get_id()) { + case td_api::inputFileLocal::ID: + return guess_file_type_by_path(static_cast(file.get())->path_); + case td_api::inputFileId::ID: { + FileId file_id(static_cast(file.get())->id_, 0); + auto file_view = get_file_view(file_id); + if (file_view.empty()) { + return FileType::Temp; + } + return file_view.get_type(); + } + case td_api::inputFileRemote::ID: { + const string &file_persistent_id = static_cast(file.get())->id_; + Result r_file_id = from_persistent_id(file_persistent_id, FileType::Temp); + if (r_file_id.is_error()) { + return FileType::Temp; + } + auto file_view = get_file_view(r_file_id.ok()); + if (file_view.empty()) { + return FileType::Temp; + } + return file_view.get_type(); + } + case td_api::inputFileGenerated::ID: + return guess_file_type_by_path(static_cast(file.get())->original_path_); + default: + UNREACHABLE(); + return FileType::Temp; + } +} + vector> FileManager::get_input_documents(const vector &file_ids) { vector> result; result.reserve(file_ids.size()); @@ -3436,7 +3512,7 @@ void FileManager::on_upload_ok(QueryId query_id, FileType file_type, const Parti file_info->download_priority_ = 0; FileView file_view(file_node); - string file_name = get_file_name(file_type, file_view.suggested_name()); + string file_name = get_file_name(file_type, file_view.suggested_path()); if (file_view.is_encrypted_secret()) { tl_object_ptr input_file; diff --git a/td/telegram/files/FileManager.h b/td/telegram/files/FileManager.h index 4aab9c9e3..882899367 100644 --- a/td/telegram/files/FileManager.h +++ b/td/telegram/files/FileManager.h @@ -120,7 +120,7 @@ class FileNode { void on_pmc_flushed(); void on_info_flushed(); - string suggested_name() const; + string suggested_path() const; bool empty = false; private: @@ -158,6 +158,7 @@ class FileNode { double last_successful_force_reupload_time_ = -1e10; FileId upload_pause_; + int8 upload_priority_ = 0; int8 download_priority_ = 0; int8 generate_priority_ = 0; @@ -185,6 +186,8 @@ class FileNode { bool upload_was_update_file_reference_{false}; bool download_was_update_file_reference_{false}; + bool upload_prefer_small_{false}; + void init_ready_size(); void recalc_ready_prefix_size(int64 prefix_offset, int64 ready_prefix_size); @@ -259,7 +262,7 @@ class FileView { const string &remote_name() const; - string suggested_name() const; + string suggested_path() const; DialogId owner_dialog_id() const; @@ -454,7 +457,7 @@ class FileManager : public FileLoadManager::Callback { int64 limit); void upload(FileId file_id, std::shared_ptr callback, int32 new_priority, uint64 upload_order); void resume_upload(FileId file_id, std::vector bad_parts, std::shared_ptr callback, - int32 new_priority, uint64 upload_order, bool force = false); + int32 new_priority, uint64 upload_order, bool force = false, bool prefer_small = false); void cancel_upload(FileId file_id); bool delete_partial_remote_location(FileId file_id); void delete_file_reference(FileId file_id, std::string file_reference); @@ -484,6 +487,8 @@ class FileManager : public FileLoadManager::Callback { Result get_map_thumbnail_file_id(Location location, int32 zoom, int32 width, int32 height, int32 scale, DialogId owner_dialog_id) TD_WARN_UNUSED_RESULT; + FileType guess_file_type(const tl_object_ptr &file); + vector> get_input_documents(const vector &file_ids); static bool extract_was_uploaded(const tl_object_ptr &input_media); @@ -505,7 +510,7 @@ class FileManager : public FileLoadManager::Callback { private: void destroy_query(int32 file_id); - + void memory_cleanup(bool full); Result check_input_file_id(FileType type, Result result, bool is_encrypted, bool allow_zero, diff --git a/td/telegram/files/FileUploader.cpp b/td/telegram/files/FileUploader.cpp index 7cad1b0e1..72860950e 100644 --- a/td/telegram/files/FileUploader.cpp +++ b/td/telegram/files/FileUploader.cpp @@ -184,7 +184,7 @@ Result FileUploader::on_update_local_location(const Loca } local_size_ = local_size; - if (expected_size_ < local_size_) { + if (expected_size_ < local_size_ && (expected_size_ != (10 << 20) || local_size_ >= (30 << 20))) { expected_size_ = local_size_; } local_is_ready_ = local_is_ready; diff --git a/td/telegram/logevent/LogEvent.h b/td/telegram/logevent/LogEvent.h index 55f5f2c25..bc592a78e 100644 --- a/td/telegram/logevent/LogEvent.h +++ b/td/telegram/logevent/LogEvent.h @@ -100,6 +100,7 @@ class LogEvent { ReadMessageThreadHistoryOnServer = 0x119, BlockMessageSenderFromRepliesOnServer = 0x120, UnpinAllDialogMessagesOnServer = 0x121, + DeleteAllCallMessagesFromServer = 0x122, GetChannelDifference = 0x140, AddMessagePushNotification = 0x200, EditMessagePushNotification = 0x201, diff --git a/td/telegram/logevent/SecretChatEvent.h b/td/telegram/logevent/SecretChatEvent.h index c65b5d521..2c8f241f2 100644 --- a/td/telegram/logevent/SecretChatEvent.h +++ b/td/telegram/logevent/SecretChatEvent.h @@ -34,12 +34,8 @@ class SecretChatEvent : public LogEventBase { virtual Type get_type() const = 0; - static constexpr LogEvent::HandlerType get_handler_type() { - return LogEvent::HandlerType::SecretChats; - } - static constexpr int32 version() { - return 2; + return 3; } template @@ -354,7 +350,7 @@ class OutboundSecretMessage : public SecretChatLogEventBase(action); + bool has_action = action != nullptr; BEGIN_STORE_FLAGS(); STORE_FLAG(is_sent); STORE_FLAG(need_notify_user); @@ -414,21 +410,34 @@ class CloseSecretChat : public SecretChatLogEventBase { public: static constexpr Type type = SecretChatEvent::Type::CloseSecretChat; int32 chat_id = 0; + bool delete_history = false; + bool is_already_discarded = false; template void store(StorerT &storer) const { using td::store; + BEGIN_STORE_FLAGS(); + STORE_FLAG(delete_history); + STORE_FLAG(is_already_discarded); + END_STORE_FLAGS(); store(chat_id, storer); } template void parse(ParserT &parser) { using td::parse; + if (parser.version() >= 3) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(delete_history); + PARSE_FLAG(is_already_discarded); + END_PARSE_FLAGS(); + } parse(chat_id, parser); } StringBuilder &print(StringBuilder &sb) const override { - return sb << "[Logevent CloseSecretChat " << tag("id", log_event_id()) << tag("chat_id", chat_id) << "]"; + return sb << "[Logevent CloseSecretChat " << tag("id", log_event_id()) << tag("chat_id", chat_id) + << tag("delete_history", delete_history) << tag("is_already_discarded", is_already_discarded) << "]"; } }; diff --git a/td/telegram/net/AuthDataShared.cpp b/td/telegram/net/AuthDataShared.cpp index 35f545ed2..aaf628cae 100644 --- a/td/telegram/net/AuthDataShared.cpp +++ b/td/telegram/net/AuthDataShared.cpp @@ -41,12 +41,9 @@ class AuthDataSharedImpl : public AuthDataShared { } return res; } - using AuthDataShared::get_auth_key_state; - std::pair get_auth_key_state() override { - // TODO (perf): - auto auth_key = get_auth_key(); - AuthKeyState state = get_auth_key_state(auth_key); - return std::make_pair(state, auth_key.was_auth_flag()); + + AuthKeyState get_auth_key_state() override { + return AuthDataShared::get_auth_key_state(get_auth_key()); } void set_auth_key(const mtproto::AuthKey &auth_key) override { @@ -106,7 +103,8 @@ class AuthDataSharedImpl : public AuthDataShared { } void log_auth_key(const mtproto::AuthKey &auth_key) { - LOG(WARNING) << dc_id_ << " " << tag("auth_key_id", auth_key.id()) << tag("state", get_auth_key_state(auth_key)) + LOG(WARNING) << dc_id_ << " " << tag("auth_key_id", auth_key.id()) + << tag("state", AuthDataShared::get_auth_key_state(auth_key)) << tag("created_at", auth_key.created_at()); } }; diff --git a/td/telegram/net/AuthDataShared.h b/td/telegram/net/AuthDataShared.h index 9266f4ead..1fdb2d42e 100644 --- a/td/telegram/net/AuthDataShared.h +++ b/td/telegram/net/AuthDataShared.h @@ -51,7 +51,7 @@ class AuthDataShared { virtual DcId dc_id() const = 0; virtual const std::shared_ptr &public_rsa_key() = 0; virtual mtproto::AuthKey get_auth_key() = 0; - virtual std::pair get_auth_key_state() = 0; + virtual AuthKeyState get_auth_key_state() = 0; virtual void set_auth_key(const mtproto::AuthKey &auth_key) = 0; virtual void update_server_time_difference(double diff) = 0; virtual double get_server_time_difference() = 0; diff --git a/td/telegram/net/DcAuthManager.cpp b/td/telegram/net/DcAuthManager.cpp index 0e9f83511..4cdb0d9ac 100644 --- a/td/telegram/net/DcAuthManager.cpp +++ b/td/telegram/net/DcAuthManager.cpp @@ -8,7 +8,6 @@ #include "td/actor/actor.h" -#include "td/telegram/ConfigShared.h" #include "td/telegram/Global.h" #include "td/telegram/net/AuthDataShared.h" #include "td/telegram/net/NetQuery.h" @@ -64,11 +63,8 @@ void DcAuthManager::add_dc(std::shared_ptr auth_data) { info.dc_id = auth_data->dc_id(); CHECK(info.dc_id.is_exact()); info.shared_auth_data = std::move(auth_data); - auto state_was_auth = info.shared_auth_data->get_auth_key_state(); - info.auth_key_state = state_was_auth.first; - VLOG(dc) << "Add " << info.dc_id << " with auth key state " << info.auth_key_state - << " and was_auth = " << state_was_auth.second; - was_auth_ |= state_was_auth.second; + info.auth_key_state = info.shared_auth_data->get_auth_key_state(); + VLOG(dc) << "Add " << info.dc_id << " with auth key state " << info.auth_key_state; if (!main_dc_id_.is_exact()) { main_dc_id_ = info.dc_id; VLOG(dc) << "Set main DcId to " << main_dc_id_; @@ -100,11 +96,8 @@ DcAuthManager::DcInfo *DcAuthManager::find_dc(int32 dc_id) { void DcAuthManager::update_auth_key_state() { int32 dc_id = narrow_cast(get_link_token()); auto &dc = get_dc(dc_id); - auto state_was_auth = dc.shared_auth_data->get_auth_key_state(); - VLOG(dc) << "Update " << dc_id << " auth key state from " << dc.auth_key_state << " to " << state_was_auth.first - << " with was_auth = " << state_was_auth.second; - dc.auth_key_state = state_was_auth.first; - was_auth_ |= state_was_auth.second; + dc.auth_key_state = dc.shared_auth_data->get_auth_key_state(); + VLOG(dc) << "Update " << dc_id << " auth key state from " << dc.auth_key_state << " to " << dc.auth_key_state; loop(); } @@ -239,15 +232,8 @@ void DcAuthManager::loop() { } auto main_dc = find_dc(main_dc_id_.get_raw_id()); if (!main_dc || main_dc->auth_key_state != AuthKeyState::OK) { - VLOG(dc) << "Main is " << main_dc_id_ << ", main auth key state is " - << (main_dc ? main_dc->auth_key_state : AuthKeyState::Empty) << ", was_auth = " << was_auth_; - if (was_auth_) { - G()->shared_config().set_option_boolean("auth", false); - destroy_loop(); - } - VLOG(dc) << "Skip loop because auth state of main DcId " << main_dc_id_.get_raw_id() << " is " - << (main_dc != nullptr ? (PSTRING() << main_dc->auth_key_state) : "unknown"); - + VLOG(dc) << "Skip loop, because main DC is " << main_dc_id_ << ", main auth key state is " + << (main_dc != nullptr ? main_dc->auth_key_state : AuthKeyState::Empty); return; } for (auto &dc : dcs_) { diff --git a/td/telegram/net/DcAuthManager.h b/td/telegram/net/DcAuthManager.h index 2adb14f45..9d420dd55 100644 --- a/td/telegram/net/DcAuthManager.h +++ b/td/telegram/net/DcAuthManager.h @@ -47,7 +47,6 @@ class DcAuthManager : public NetQueryCallback { ActorShared<> parent_; std::vector dcs_; - bool was_auth_{false}; DcId main_dc_id_; bool close_flag_{false}; Promise<> destroy_promise_; diff --git a/td/telegram/net/PublicRsaKeyWatchdog.cpp b/td/telegram/net/PublicRsaKeyWatchdog.cpp index 34561d5eb..eccd1257b 100644 --- a/td/telegram/net/PublicRsaKeyWatchdog.cpp +++ b/td/telegram/net/PublicRsaKeyWatchdog.cpp @@ -77,7 +77,7 @@ void PublicRsaKeyWatchdog::on_result(NetQueryPtr net_query) { has_query_ = false; yield(); if (net_query->is_error()) { - LOG(ERROR) << "Receive error for GetCdnConfigQuery: " << net_query->move_as_error(); + LOG(ERROR) << "Receive error for GetCdnConfig: " << net_query->move_as_error(); return; } diff --git a/td/telegram/net/Session.cpp b/td/telegram/net/Session.cpp index 95287a389..1c62b7596 100644 --- a/td/telegram/net/Session.cpp +++ b/td/telegram/net/Session.cpp @@ -8,6 +8,7 @@ #include "td/telegram/telegram_api.h" +#include "td/telegram/ConfigShared.h" #include "td/telegram/DhCache.h" #include "td/telegram/Global.h" #include "td/telegram/net/DcAuthManager.h" @@ -778,6 +779,7 @@ void Session::on_message_result_error(uint64 id, int error_code, BufferSlice mes LOG(WARNING) << "Lost authorization due to " << tag("msg", message.as_slice()); } auth_data_.set_auth_flag(false); + G()->shared_config().set_option_boolean("auth", false); shared_auth_data_->set_auth_key(auth_data_.get_main_auth_key()); on_session_failed(Status::OK()); } diff --git a/td/telegram/net/SessionProxy.cpp b/td/telegram/net/SessionProxy.cpp index 2be3e9e46..44baab333 100644 --- a/td/telegram/net/SessionProxy.cpp +++ b/td/telegram/net/SessionProxy.cpp @@ -102,7 +102,7 @@ void SessionProxy::start_up() { private: ActorShared session_proxy_; }; - auth_key_state_ = auth_data_->get_auth_key_state().first; + auth_key_state_ = auth_data_->get_auth_key_state(); auth_data_->add_auth_key_listener(make_unique(actor_shared(this))); open_session(); } @@ -212,7 +212,7 @@ void SessionProxy::open_session(bool force) { void SessionProxy::update_auth_key_state() { auto old_auth_key_state = auth_key_state_; - auth_key_state_ = auth_data_->get_auth_key_state().first; + auth_key_state_ = auth_data_->get_auth_key_state(); if (auth_key_state_ != old_auth_key_state && old_auth_key_state == AuthKeyState::OK) { close_session(); } diff --git a/tddb/td/db/binlog/BinlogEvent.cpp b/tddb/td/db/binlog/BinlogEvent.cpp index 246c14110..3333a821f 100644 --- a/tddb/td/db/binlog/BinlogEvent.cpp +++ b/tddb/td/db/binlog/BinlogEvent.cpp @@ -28,8 +28,7 @@ Status BinlogEvent::init(BufferSlice &&raw_event, bool check_crc) { data_ = MutableSlice(const_cast(slice_data.begin()), slice_data.size()); crc32_ = static_cast(parser.fetch_int()); if (check_crc) { - CHECK(size_ >= TAIL_SIZE); - auto calculated_crc = crc32(raw_event.as_slice().truncate(size_ - TAIL_SIZE)); + auto calculated_crc = crc32(raw_event.as_slice().substr(0, size_ - TAIL_SIZE)); if (calculated_crc != crc32_) { return Status::Error(PSLICE() << "crc mismatch " << tag("actual", format::as_hex(calculated_crc)) << tag("expected", format::as_hex(crc32_)) << public_to_string()); @@ -44,7 +43,7 @@ Status BinlogEvent::validate() const { if (raw_event_.size() < 4) { return Status::Error("Too small event"); } - uint32 size = TlParser(raw_event_.as_slice().truncate(4)).fetch_int(); + uint32 size = TlParser(raw_event_.as_slice().substr(0, 4)).fetch_int(); if (size_ != size) { return Status::Error(PSLICE() << "Size of event changed: " << tag("was", size_) << tag("now", size)); } diff --git a/tdutils/generate/CMakeLists.txt b/tdutils/generate/CMakeLists.txt index acad005c3..c0254e925 100644 --- a/tdutils/generate/CMakeLists.txt +++ b/tdutils/generate/CMakeLists.txt @@ -51,7 +51,7 @@ if (NOT CMAKE_CROSSCOMPILING) OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/auto/mime_type_to_extension.cpp WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${MIME_TYPE_TO_EXTENSION_CMD} - DEPENDS auto/mime_type_to_extension.gperf + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/auto/mime_type_to_extension.gperf ) if (CMAKE_HOST_WIN32) @@ -63,6 +63,6 @@ if (NOT CMAKE_CROSSCOMPILING) OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/auto/extension_to_mime_type.cpp WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${EXTENSION_TO_MIME_TYPE_CMD} - DEPENDS auto/extension_to_mime_type.gperf + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/auto/extension_to_mime_type.gperf ) endif() diff --git a/tdutils/generate/mime_types.txt b/tdutils/generate/mime_types.txt index 188845c27..18dcfb775 100644 --- a/tdutils/generate/mime_types.txt +++ b/tdutils/generate/mime_types.txt @@ -594,7 +594,7 @@ audio/basic au snd audio/midi mid midi kar rmi audio/mp4 m4a mp4a audio/mpeg mpga mp2 mp2a mp3 m2a m3a -audio/ogg oga ogg spx +audio/ogg oga ogg opus spx audio/s3m s3m audio/silk sil audio/vnd.dece.audio uva uvva diff --git a/tdutils/td/utils/BigNum.cpp b/tdutils/td/utils/BigNum.cpp index 7578cc241..d29ef7502 100644 --- a/tdutils/td/utils/BigNum.cpp +++ b/tdutils/td/utils/BigNum.cpp @@ -147,7 +147,7 @@ bool BigNum::is_bit_set(int num) const { } bool BigNum::is_prime(BigNumContext &context) const { -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) int result = BN_check_prime(impl_->big_num, context.impl_->big_num_context, nullptr); #else int result = diff --git a/tdutils/td/utils/BufferedFd.h b/tdutils/td/utils/BufferedFd.h index 014c0667a..cd092ea55 100644 --- a/tdutils/td/utils/BufferedFd.h +++ b/tdutils/td/utils/BufferedFd.h @@ -101,7 +101,8 @@ Result BufferedFdBase::flush_read(size_t max_read) { CHECK(read_); size_t result = 0; while (::td::can_read_local(*this) && max_read) { - MutableSlice slice = read_->prepare_append().truncate(max_read); + MutableSlice slice = read_->prepare_append(); + slice.truncate(max_read); TRY_RESULT(x, FdT::read(slice)); slice.truncate(x); read_->confirm_append(x); diff --git a/tdutils/td/utils/BufferedUdp.h b/tdutils/td/utils/BufferedUdp.h index 348f89035..b99815baa 100644 --- a/tdutils/td/utils/BufferedUdp.h +++ b/tdutils/td/utils/BufferedUdp.h @@ -36,7 +36,7 @@ class UdpWriter { } size_t cnt; - auto status = fd.send_messages(::td::Span(messages).truncate(to_send_n), cnt); + auto status = fd.send_messages(Span(messages).truncate(to_send_n), cnt); queue.pop_n(cnt); return status; } @@ -51,7 +51,7 @@ class UdpReaderHelper { buffer_ = BufferSlice(RESERVED_SIZE); } CHECK(buffer_.size() >= MAX_PACKET_SIZE); - message.data = buffer_.as_slice().truncate(MAX_PACKET_SIZE); + message.data = buffer_.as_slice().substr(0, MAX_PACKET_SIZE); } UdpMessage extract_udp_message(UdpSocketFd::InboundMessage &message) { diff --git a/tdutils/td/utils/base64.cpp b/tdutils/td/utils/base64.cpp index e3eda3b3c..92f8e1d13 100644 --- a/tdutils/td/utils/base64.cpp +++ b/tdutils/td/utils/base64.cpp @@ -156,6 +156,9 @@ Result base64_decode_secure(Slice base64) { Result base64url_decode(Slice base64) { return base64_decode_impl(base64); } +Result base64url_decode_secure(Slice base64) { + return base64_decode_impl(base64); +} template static bool is_base64_impl(Slice input) { diff --git a/tdutils/td/utils/base64.h b/tdutils/td/utils/base64.h index d67115c6f..ec67dc6c9 100644 --- a/tdutils/td/utils/base64.h +++ b/tdutils/td/utils/base64.h @@ -19,6 +19,7 @@ Result base64_decode_secure(Slice base64); string base64url_encode(Slice input); Result base64url_decode(Slice base64); +Result base64url_decode_secure(Slice base64); bool is_base64(Slice input); bool is_base64url(Slice input); diff --git a/tdutils/td/utils/crypto.cpp b/tdutils/td/utils/crypto.cpp index 043a0b037..7b033ad0b 100644 --- a/tdutils/td/utils/crypto.cpp +++ b/tdutils/td/utils/crypto.cpp @@ -34,7 +34,7 @@ #include #endif -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) #include #include #endif @@ -680,7 +680,7 @@ void AesCtrState::decrypt(Slice from, MutableSlice to) { encrypt(from, to); } -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) static void make_digest(Slice data, MutableSlice output, const EVP_MD *evp_md) { EVP_MD_CTX *ctx = EVP_MD_CTX_new(); LOG_IF(FATAL, ctx == nullptr); @@ -695,7 +695,7 @@ static void make_digest(Slice data, MutableSlice output, const EVP_MD *evp_md) { #endif void sha1(Slice data, unsigned char output[20]) { -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) make_digest(data, MutableSlice(output, 20), EVP_sha1()); #else auto result = SHA1(data.ubegin(), data.size(), output); @@ -705,7 +705,7 @@ void sha1(Slice data, unsigned char output[20]) { void sha256(Slice data, MutableSlice output) { CHECK(output.size() >= 32); -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) make_digest(data, output, EVP_sha256()); #else auto result = SHA256(data.ubegin(), data.size(), output.ubegin()); @@ -715,7 +715,7 @@ void sha256(Slice data, MutableSlice output) { void sha512(Slice data, MutableSlice output) { CHECK(output.size() >= 64); -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) make_digest(data, output, EVP_sha512()); #else auto result = SHA512(data.ubegin(), data.size(), output.ubegin()); @@ -737,7 +737,7 @@ string sha512(Slice data) { class Sha256State::Impl { public: -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) EVP_MD_CTX *ctx_; Impl() { @@ -789,7 +789,7 @@ void Sha256State::init() { impl_ = make_unique(); } CHECK(!is_inited_); -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) int err = EVP_DigestInit_ex(impl_->ctx_, EVP_sha256(), nullptr); #else int err = SHA256_Init(&impl_->ctx_); @@ -801,7 +801,7 @@ void Sha256State::init() { void Sha256State::feed(Slice data) { CHECK(impl_); CHECK(is_inited_); -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) int err = EVP_DigestUpdate(impl_->ctx_, data.ubegin(), data.size()); #else int err = SHA256_Update(&impl_->ctx_, data.ubegin(), data.size()); @@ -813,7 +813,7 @@ void Sha256State::extract(MutableSlice output, bool destroy) { CHECK(output.size() >= 32); CHECK(impl_); CHECK(is_inited_); -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) int err = EVP_DigestFinal_ex(impl_->ctx_, output.ubegin(), nullptr); #else int err = SHA256_Final(output.ubegin(), &impl_->ctx_); @@ -827,7 +827,7 @@ void Sha256State::extract(MutableSlice output, bool destroy) { void md5(Slice input, MutableSlice output) { CHECK(output.size() >= 16); -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) make_digest(input, output, EVP_md5()); #else auto result = MD5(input.ubegin(), input.size(), output.ubegin()); @@ -880,7 +880,7 @@ void pbkdf2_sha512(Slice password, Slice salt, int iteration_count, MutableSlice pbkdf2_impl(password, salt, iteration_count, dest, EVP_sha512()); } -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) static void hmac_impl(const char *digest, Slice key, Slice message, MutableSlice dest) { EVP_MAC *hmac = EVP_MAC_fetch(nullptr, "HMAC", nullptr); LOG_IF(FATAL, hmac == nullptr); @@ -918,7 +918,7 @@ static void hmac_impl(const EVP_MD *evp_md, Slice key, Slice message, MutableSli void hmac_sha256(Slice key, Slice message, MutableSlice dest) { CHECK(dest.size() == 256 / 8); -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) hmac_impl("SHA256", key, message, dest); #else hmac_impl(EVP_sha256(), key, message, dest); @@ -927,7 +927,7 @@ void hmac_sha256(Slice key, Slice message, MutableSlice dest) { void hmac_sha512(Slice key, Slice message, MutableSlice dest) { CHECK(dest.size() == 512 / 8); -#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) hmac_impl("SHA512", key, message, dest); #else hmac_impl(EVP_sha512(), key, message, dest); diff --git a/tdutils/td/utils/port/StdStreams.cpp b/tdutils/td/utils/port/StdStreams.cpp index 5fcd71441..df73a643f 100644 --- a/tdutils/td/utils/port/StdStreams.cpp +++ b/tdutils/td/utils/port/StdStreams.cpp @@ -201,7 +201,8 @@ class BufferedStdinImpl { size_t result = 0; ::td::sync_with_poll(*this); while (::td::can_read_local(*this) && max_read) { - MutableSlice slice = writer_.prepare_append().truncate(max_read); + MutableSlice slice = writer_.prepare_append(); + slice.truncate(max_read); TRY_RESULT(x, file_fd_.read(slice)); slice.truncate(x); writer_.confirm_append(x); diff --git a/tdutils/td/utils/translit.cpp b/tdutils/td/utils/translit.cpp index 239fa7f5c..60ee4e78a 100644 --- a/tdutils/td/utils/translit.cpp +++ b/tdutils/td/utils/translit.cpp @@ -7,7 +7,6 @@ #include "td/utils/translit.h" #include "td/utils/algorithm.h" -#include "td/utils/format.h" #include "td/utils/misc.h" #include "td/utils/utf8.h" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 33b3fa426..7ad2117ea 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -32,6 +32,20 @@ set(TESTS_MAIN if (NOT CMAKE_CROSSCOMPILING OR EMSCRIPTEN) #Tests + if (OPENSSL_FOUND) + add_executable(test-crypto EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/crypto.cpp) + target_include_directories(test-crypto SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR}) + target_link_libraries(test-crypto PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES} tdutils tdcore) + + if (WIN32) + if (MINGW) + target_link_libraries(test-crypto PRIVATE ws2_32 mswsock crypt32) + else() + target_link_libraries(test-crypto PRIVATE ws2_32 Mswsock Crypt32) + endif() + endif() + endif() + add_executable(test-tdutils EXCLUDE_FROM_ALL ${TESTS_MAIN} ${TDUTILS_TEST_SOURCE}) add_executable(test-online EXCLUDE_FROM_ALL online.cpp) add_executable(run_all_tests ${TESTS_MAIN} ${TD_TEST_SOURCE}) diff --git a/test/crypto.cpp b/test/crypto.cpp new file mode 100644 index 000000000..99e5ba5bd --- /dev/null +++ b/test/crypto.cpp @@ -0,0 +1,280 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/mtproto/AuthKey.h" +#include "td/mtproto/Transport.h" + +#include "td/utils/base64.h" +#include "td/utils/common.h" +#include "td/utils/crypto.h" +#include "td/utils/logging.h" +#include "td/utils/ScopeGuard.h" +#include "td/utils/SharedSlice.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/UInt.h" + +#include +#include +#include + +class Handshake { + public: + struct KeyPair { + td::SecureString private_key; + td::SecureString public_key; + }; + + static td::Result generate_key_pair() { + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(NID_X25519, nullptr); + if (pctx == nullptr) { + return td::Status::Error("Can't create EXP_PKEY_CTX"); + } + SCOPE_EXIT { + EVP_PKEY_CTX_free(pctx); + }; + if (EVP_PKEY_keygen_init(pctx) <= 0) { + return td::Status::Error("Can't init keygen"); + } + + EVP_PKEY *pkey = nullptr; + if (EVP_PKEY_keygen(pctx, &pkey) <= 0) { + return td::Status::Error("Can't generate key"); + } + + TRY_RESULT(private_key, X25519_key_from_PKEY(pkey, true)); + TRY_RESULT(public_key, X25519_key_from_PKEY(pkey, false)); + + KeyPair res; + res.private_key = std::move(private_key); + res.public_key = std::move(public_key); + + return res; + } + + static td::SecureString expand_secret(td::Slice secret) { + td::SecureString res(128); + td::hmac_sha512(secret, "0", res.as_mutable_slice().substr(0, 64)); + td::hmac_sha512(secret, "1", res.as_mutable_slice().substr(64, 64)); + return res; + } + + static td::Result privateKeyToPem(td::Slice key) { + auto pkey_private = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, nullptr, key.ubegin(), 32); + CHECK(pkey_private != nullptr); + auto res = X25519_pem_from_PKEY(pkey_private, true); + EVP_PKEY_free(pkey_private); + return res; + } + + static td::Result calc_shared_secret(td::Slice private_key, td::Slice other_public_key) { + auto pkey_private = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, nullptr, private_key.ubegin(), 32); + if (pkey_private == nullptr) { + return td::Status::Error("Invalid X25520 private key"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey_private); + }; + + auto pkey_public = + EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, nullptr, other_public_key.ubegin(), other_public_key.size()); + if (pkey_public == nullptr) { + return td::Status::Error("Invalid X25519 public key"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey_public); + }; + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey_private, nullptr); + if (ctx == nullptr) { + return td::Status::Error("Can't create EVP_PKEY_CTX"); + } + SCOPE_EXIT { + EVP_PKEY_CTX_free(ctx); + }; + + if (EVP_PKEY_derive_init(ctx) <= 0) { + return td::Status::Error("Can't init derive"); + } + if (EVP_PKEY_derive_set_peer(ctx, pkey_public) <= 0) { + return td::Status::Error("Can't init derive"); + } + + size_t result_len = 0; + if (EVP_PKEY_derive(ctx, nullptr, &result_len) <= 0) { + return td::Status::Error("Can't get result length"); + } + if (result_len != 32) { + return td::Status::Error("Unexpected result length"); + } + + td::SecureString result(result_len, '\0'); + if (EVP_PKEY_derive(ctx, result.as_mutable_slice().ubegin(), &result_len) <= 0) { + return td::Status::Error("Failed to compute shared secret"); + } + return std::move(result); + } + + private: + static td::Result X25519_key_from_PKEY(EVP_PKEY *pkey, bool is_private) { + auto func = is_private ? &EVP_PKEY_get_raw_private_key : &EVP_PKEY_get_raw_public_key; + size_t len = 0; + if (func(pkey, nullptr, &len) == 0) { + return td::Status::Error("Failed to get raw key length"); + } + CHECK(len == 32); + + td::SecureString result(len); + if (func(pkey, result.as_mutable_slice().ubegin(), &len) == 0) { + return td::Status::Error("Failed to get raw key"); + } + return std::move(result); + } + static td::Result X25519_pem_from_PKEY(EVP_PKEY *pkey, bool is_private) { + BIO *mem_bio = BIO_new(BIO_s_mem()); + SCOPE_EXIT { + BIO_vfree(mem_bio); + }; + if (is_private) { + PEM_write_bio_PrivateKey(mem_bio, pkey, nullptr, nullptr, 0, nullptr, nullptr); + } else { + PEM_write_bio_PUBKEY(mem_bio, pkey); + } + char *data_ptr = nullptr; + auto data_size = BIO_get_mem_data(mem_bio, &data_ptr); + return td::SecureString(data_ptr, data_size); + } +}; + +struct HandshakeTest { + Handshake::KeyPair alice; + Handshake::KeyPair bob; + + td::SecureString shared_secret; + td::SecureString key; +}; + +namespace td { + +static void KDF3(Slice auth_key, const UInt128 &msg_key, int X, UInt256 *aes_key, UInt128 *aes_iv) { + uint8 buf_raw[36 + 16]; + MutableSlice buf(buf_raw, 36 + 16); + Slice msg_key_slice = as_slice(msg_key); + + // sha256_a = SHA256 (msg_key + substr(auth_key, x, 36)); + buf.copy_from(msg_key_slice); + buf.substr(16, 36).copy_from(auth_key.substr(X, 36)); + uint8 sha256_a_raw[32]; + MutableSlice sha256_a(sha256_a_raw, 32); + sha256(buf, sha256_a); + + // sha256_b = SHA256 (substr(auth_key, 40+x, 36) + msg_key); + buf.copy_from(auth_key.substr(40 + X, 36)); + buf.substr(36).copy_from(msg_key_slice); + uint8 sha256_b_raw[32]; + MutableSlice sha256_b(sha256_b_raw, 32); + sha256(buf, sha256_b); + + // aes_key = substr(sha256_a, 0, 8) + substr(sha256_b, 8, 16) + substr(sha256_a, 24, 8); + MutableSlice aes_key_slice(aes_key->raw, sizeof(aes_key->raw)); + aes_key_slice.copy_from(sha256_a.substr(0, 8)); + aes_key_slice.substr(8).copy_from(sha256_b.substr(8, 16)); + aes_key_slice.substr(24).copy_from(sha256_a.substr(24, 8)); + + // aes_iv = substr(sha256_b, 0, 4) + substr(sha256_a, 8, 8) + substr(sha256_b, 24, 4); + MutableSlice aes_iv_slice(aes_iv->raw, sizeof(aes_iv->raw)); + aes_iv_slice.copy_from(sha256_b.substr(0, 4)); + aes_iv_slice.substr(4).copy_from(sha256_a.substr(8, 8)); + aes_iv_slice.substr(12).copy_from(sha256_b.substr(24, 4)); +} + +} // namespace td + +static td::SecureString encrypt(td::Slice key, td::Slice data, td::int32 seqno, int X) { + td::SecureString res(data.size() + 4 + 16); + res.as_mutable_slice().substr(20).copy_from(data); + + // big endian + td::uint8 *ptr = res.as_mutable_slice().substr(16).ubegin(); + ptr[0] = (seqno >> 24) & 255; + ptr[1] = (seqno >> 16) & 255; + ptr[2] = (seqno >> 8) & 255; + ptr[3] = (seqno)&255; + + td::mtproto::AuthKey auth_key(0, key.str()); + auto payload = res.as_mutable_slice().substr(16); + td::UInt128 msg_key = td::mtproto::Transport::calc_message_key2(auth_key, X, payload).second; + td::UInt256 aes_key; + td::UInt128 aes_iv; + td::KDF3(key, msg_key, X, &aes_key, &aes_iv); + td::AesCtrState aes; + aes.init(aes_key.as_slice(), aes_iv.as_slice()); + aes.encrypt(payload, payload); + res.as_mutable_slice().copy_from(msg_key.as_slice()); + return res; +} + +static HandshakeTest gen_test() { + HandshakeTest res; + res.alice = Handshake::generate_key_pair().move_as_ok(); + + res.bob = Handshake::generate_key_pair().move_as_ok(); + res.shared_secret = Handshake::calc_shared_secret(res.alice.private_key, res.bob.public_key).move_as_ok(); + res.key = Handshake::expand_secret(res.shared_secret); + return res; +} + +static void run_test(const HandshakeTest &test) { + auto alice_secret = Handshake::calc_shared_secret(test.alice.private_key, test.bob.public_key).move_as_ok(); + auto bob_secret = Handshake::calc_shared_secret(test.bob.private_key, test.alice.public_key).move_as_ok(); + auto key = Handshake::expand_secret(alice_secret); + CHECK(alice_secret == bob_secret); + CHECK(alice_secret == test.shared_secret); + LOG(ERROR) << "Key\n\t" << td::base64url_encode(key) << "\n"; + CHECK(key == test.key); +} + +static td::StringBuilder &operator<<(td::StringBuilder &sb, const Handshake::KeyPair &key_pair) { + sb << "\tpublic_key (base64url) = " << td::base64url_encode(key_pair.public_key) << "\n"; + sb << "\tprivate_key (base64url) = " << td::base64url_encode(key_pair.private_key) << "\n"; + sb << "\tprivate_key (pem) = \n" << Handshake::privateKeyToPem(key_pair.private_key).ok() << "\n"; + return sb; +} + +static td::StringBuilder &operator<<(td::StringBuilder &sb, const HandshakeTest &test) { + sb << "Alice\n" << test.alice; + sb << "Bob\n" << test.bob; + sb << "SharedSecret\n\t" << td::base64url_encode(test.shared_secret) << "\n"; + sb << "Key\n\t" << td::base64url_encode(test.key) << "\n"; + + std::string data = "hello world"; + sb << "encrypt(\"" << data << "\", X=0) = \n\t" << td::base64url_encode(encrypt(test.key, data, 1, 0)) << "\n"; + sb << "encrypt(\"" << data << "\", X=8) = \n\t" << td::base64url_encode(encrypt(test.key, data, 1, 8)) << "\n"; + return sb; +} + +static HandshakeTest pregenerated_test() { + HandshakeTest test; + test.alice.public_key = td::base64url_decode_secure("QlCME5fXLyyQQWeYnBiGAZbmzuD4ayOuADCFgmioOBY").move_as_ok(); + test.alice.private_key = td::base64url_decode_secure("8NZGWKfRCJfiks74RG9_xHmYydarLiRsoq8VcJGPglg").move_as_ok(); + test.bob.public_key = td::base64url_decode_secure("I1yzfmMCZzlI7xwMj1FJ3O3I3_aEUtv6CxbHiDGzr18").move_as_ok(); + test.bob.private_key = td::base64url_decode_secure("YMGoowtnZ99roUM2y5JRwiQrwGaNJ-ZRE5boy-l4aHg").move_as_ok(); + test.shared_secret = td::base64url_decode_secure("0IIvKJuXEwmAp41fYhjUnWqLTYQJ7QeKZKYuCG8mFz8").move_as_ok(); + test.key = td::base64url_decode_secure( + "JHmD-XW9j-13G6KP0tArIhQNDRVbRkKxx0MG0pa2nOgwJHNfiggM0I0RiNIr23-1wRboRtan4WvqOHsxAt_cngYX15_" + "HYe8tJdEwHcmlnXq7LtprigzExaNJS7skfOo2irClj-7EL06-jMrhfwngSJFsak8JFSw8s6R4fwCsr50") + .move_as_ok(); + + return test; +} + +int main() { + auto test = gen_test(); + run_test(test); + run_test(pregenerated_test()); + LOG(ERROR) << "\n" << pregenerated_test(); +} diff --git a/test/message_entities.cpp b/test/message_entities.cpp index b9636fa51..d65bc320f 100644 --- a/test/message_entities.cpp +++ b/test/message_entities.cpp @@ -381,7 +381,7 @@ TEST(MessageEntities, url) { check_url("http://telegram.org", {"http://telegram.org"}); check_url("ftp://telegram.org", {"ftp://telegram.org"}); check_url("ftps://telegram.org", {}); - check_url("sftp://telegram.org", {"sftp://telegram.org"}); + check_url("sftp://telegram.org", {}); check_url("hTtPs://telegram.org", {"hTtPs://telegram.org"}); check_url("HTTP://telegram.org", {"HTTP://telegram.org"}); check_url("аHTTP://telegram.org", {"HTTP://telegram.org"}); diff --git a/test/secret.cpp b/test/secret.cpp index 6353b2151..b9d91108b 100644 --- a/test/secret.cpp +++ b/test/secret.cpp @@ -545,7 +545,7 @@ class FakeSecretChatContext : public SecretChatActor::Context { void on_send_message_ok(int64 random_id, MessageId message_id, int32 date, tl_object_ptr file, Promise<>) override; void on_delete_messages(std::vector random_id, Promise<>) override; - void on_flush_history(MessageId, Promise<>) override; + void on_flush_history(bool, MessageId, Promise<>) override; void on_read_message(int64, Promise<>) override; void on_screenshot_taken(UserId user_id, MessageId message_id, int32 date, int64 random_id, @@ -992,7 +992,7 @@ void FakeSecretChatContext::on_send_message_ok(int64 random_id, MessageId messag void FakeSecretChatContext::on_delete_messages(std::vector random_id, Promise<> promise) { promise.set_value(Unit()); } -void FakeSecretChatContext::on_flush_history(MessageId, Promise<> promise) { +void FakeSecretChatContext::on_flush_history(bool, MessageId, Promise<> promise) { promise.set_error(Status::Error("Unsupported")); } void FakeSecretChatContext::on_read_message(int64, Promise<> promise) {