From 162c06d00a295a7de2777f76cba47ade1b027354 Mon Sep 17 00:00:00 2001 From: levlam Date: Thu, 26 Nov 2020 14:32:29 +0300 Subject: [PATCH] Add joinGroupCall method. --- td/generate/scheme/td_api.tl | 19 ++- td/generate/scheme/td_api.tlo | Bin 186540 -> 187964 bytes td/telegram/GroupCallManager.cpp | 211 +++++++++++++++++++++++++++++++ td/telegram/GroupCallManager.h | 21 +++ td/telegram/Td.cpp | 9 ++ td/telegram/Td.h | 2 + td/telegram/cli.cpp | 6 + 7 files changed, 267 insertions(+), 1 deletion(-) diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index c9a98eda4..f27d9f621 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -2014,7 +2014,7 @@ callDiscardReasonHungUp = CallDiscardReason; //@udp_reflector True, if connection through UDP reflectors is supported //@min_layer The minimum supported API layer; use 65 //@max_layer The maximum supported API layer; use 65 -//@library_versions List of supported libtgvoip versions +//@library_versions List of supported tgcalls versions callProtocol udp_p2p:Bool udp_reflector:Bool min_layer:int32 max_layer:int32 library_versions:vector = CallProtocol; @@ -2062,6 +2062,20 @@ callStateError error:error = CallState; //@description Describes a group call @id Group call identifier @is_active True, if the call is active @member_count Number of members in the group call @duration Call duration; for ended calls only groupCall id:string is_active:Bool member_count:int32 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; + +//@description Describes a payload for interaction with tgcalls @ufrag Value of the field ufrag @pwd Value of the field pwd @fingerprints The list of fingerprints +groupCallPayload ufrag:string pwd:string fingerprints:vector = GroupCallPayload; + +//@description Describes a join response candidate for interaction with tgcalls @port Value of the field port @protocol Value of the field protocol @network Value of the field network +//@generation Value of the field generation @id Value of the field id @component Value of the field component @foundation Value of the field foundation @priority Value of the field priority +//@ip Value of the field ip @type Value of the field type @tcp_type Value of the field tcp_type @rel_addr Value of the field rel_addr @rel_port Value of the field rel_port +groupCallJoinResponseCandidate port:string protocol:string network:string generation:string id:string component:string foundation:string priority:string ip:string type:string tcp_type:string rel_addr:string rel_port:string = GroupCallJoinResponseCandidate; + +//@description Describes a join response for interaction with tgcalls @payload Join response payload to pass to tgcalls @candidates Join response candidates to pass to tgcalls +groupCallJoinResponse payload:groupCallPayload candidates:vector = GroupCallJoinResponse; + //@class CallProblem @description Describes the exact type of a problem with a call @@ -4293,6 +4307,9 @@ sendCallDebugInformation call_id:int32 debug_information:string = Ok; //@description Creates a group call in a chat. Available only for supergroups; requires can_manage_calls rights @chat_id Chat identifier createChatGroupCall chat_id:int53 = GroupCallId; +//@description Joins a group call @group_call_id Group call identifier @payload Group join payload, received from tgcalls @source Caller source identifier, received from tgcalls @is_muted True, if the user's microphone is muted +joinGroupCall group_call_id:string payload:groupCallPayload source:int32 is_muted:Bool = GroupCallJoinResponse; + //@description Leaves a group call @group_call_id Group call identifier @source Caller source identifier leaveGroupCall group_call_id:string source:int32 = Ok; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index f9208e19c09002e91dd9070155f867db801b9c95..5592f7629756669a0e068059792e3f9cd713de88 100644 GIT binary patch delta 776 zcmZ3pk$cY`Zr(?;^{p77KxredwvdeE^|UXd?nU{f1cst$q9z^(s|4W60un28@)J`gU*wTim^n-MpESCR zTV`H*YEeN^W?sp}0}`7pgeJ%{hHgG-=-jLdc2PQZ7lGt%9ux9mny%QvBrXZElo9G3 z>1X#fxsstyn*OkbNj?zdbeMFJGn)>FZQDVR39t~jD#sNA_8wN-;ND{ZsR7x_JDJg6 ze0t4%Mxp8VM3{u8zxl-|$ex#4Ql4LwJ^8^QNsjc?ywsw^lFaFWLX6VWc$s9lIg|5q zfuWh2SHdv){vj!jwEWUMV30vnOxL-}Br;hiKwz@P44LT(+ZhEwh6{3(Bp1Y&R2HO8 zFPzUP!%>u)6Q7upQiR|Yt%NcUY2O zV1Ok9mg#|Vj0V&1eq-ck1*e6{g>BN)Gt7Wtm{}a3TUwHuGFh-%o&}_SyH68SLKpz0 C5fq#N delta 59 zcmdn9hkMONZr(?;^{p77KyD+iw$Nq);R*7Lftwc^IX5@o*tz}2PR5RS#?I}Dl1ztl Lx4&p-3JC)MJ|Yz! diff --git a/td/telegram/GroupCallManager.cpp b/td/telegram/GroupCallManager.cpp index b002e7d04..891b76efe 100644 --- a/td/telegram/GroupCallManager.cpp +++ b/td/telegram/GroupCallManager.cpp @@ -8,9 +8,11 @@ #include "td/telegram/ContactsManager.h" #include "td/telegram/Global.h" +#include "td/telegram/misc.h" #include "td/telegram/Td.h" #include "td/telegram/UpdatesManager.h" +#include "td/utils/JsonBuilder.h" #include "td/utils/Random.h" namespace td { @@ -60,6 +62,43 @@ class CreateGroupCallQuery : public Td::ResultHandler { } }; +class JoinGroupCallQuery : public Td::ResultHandler { + Promise promise_; + InputGroupCallId group_call_id_; + uint64 generation_ = 0; + + public: + explicit JoinGroupCallQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(InputGroupCallId group_call_id, const string &payload, bool is_muted, uint64 generation) { + group_call_id_ = group_call_id; + generation_ = generation; + + int32 flags = 0; + if (is_muted) { + flags |= telegram_api::phone_joinGroupCall::MUTED_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::phone_joinGroupCall(flags, false /*ignored*/, group_call_id.get_input_group_call(), + make_tl_object(payload)))); + } + + 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->group_call_manager_->process_join_group_call_response(group_call_id_, generation_, result_ptr.move_as_ok(), + std::move(promise_)); + } + + void on_error(uint64 id, Status status) override { + promise_.set_error(std::move(status)); + } +}; + class LeaveGroupCallQuery : public Td::ResultHandler { Promise promise_; @@ -140,6 +179,175 @@ void GroupCallManager::create_group_call(ChannelId channel_id, Promisecreate_handler(std::move(promise))->send(channel_id); } +void GroupCallManager::join_group_call(InputGroupCallId group_call_id, + td_api::object_ptr &&payload, int32 source, + bool is_muted, + Promise> &&promise) { + if (pending_join_requests_.count(group_call_id)) { + auto it = pending_join_requests_.find(group_call_id); + CHECK(it != pending_join_requests_.end()); + it->second.promise.set_error(Status::Error(200, "Cancelled by another joinGroupCall request")); + pending_join_requests_.erase(it); + } + + if (payload == nullptr) { + return promise.set_error(Status::Error(400, "Payload must be non-empty")); + } + if (!clean_input_string(payload->ufrag_)) { + return promise.set_error(Status::Error(400, "Payload ufrag must be encoded in UTF-8")); + } + if (!clean_input_string(payload->pwd_)) { + return promise.set_error(Status::Error(400, "Payload pwd must be encoded in UTF-8")); + } + for (auto &fingerprint : payload->fingerprints_) { + if (fingerprint == nullptr) { + return promise.set_error(Status::Error(400, "Payload fingerprint must be non-empty")); + } + if (!clean_input_string(fingerprint->hash_)) { + return promise.set_error(Status::Error(400, "Fingerprint hash must be encoded in UTF-8")); + } + if (!clean_input_string(fingerprint->setup_)) { + return promise.set_error(Status::Error(400, "Fingerprint setup must be encoded in UTF-8")); + } + if (!clean_input_string(fingerprint->fingerprint_)) { + return promise.set_error(Status::Error(400, "Fingerprint must be encoded in UTF-8")); + } + } + + auto json_payload = json_encode(json_object([&payload, source](auto &o) { + o("ufrag", payload->ufrag_); + o("pwd", payload->pwd_); + o("fingerprints", json_array(payload->fingerprints_, + [](const td_api::object_ptr &fingerprint) { + return json_object([&fingerprint](auto &o) { + o("hash", fingerprint->hash_); + o("setup", fingerprint->setup_); + o("fingerprint", fingerprint->fingerprint_); + }); + })); + o("ssrc", source); + })); + + auto generation = join_group_request_generation_++; + auto &request = pending_join_requests_[group_call_id]; + request.generation = generation; + request.promise = std::move(promise); + + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), generation, group_call_id](Result &&result) { + CHECK(result.is_error()); + send_closure(actor_id, &GroupCallManager::finish_join_group_call, group_call_id, generation, + result.move_as_error()); + }); + td_->create_handler(std::move(query_promise)) + ->send(group_call_id, json_payload, is_muted, generation); +} + +void GroupCallManager::process_join_group_call_response(InputGroupCallId group_call_id, uint64 generation, + tl_object_ptr &&updates, + Promise &&promise) { + auto it = pending_join_requests_.find(group_call_id); + if (it == pending_join_requests_.end() || it->second.generation != generation) { + LOG(INFO) << "Ignore JoinGroupCallQuery response with " << group_call_id << " and generation " << generation; + return; + } + + LOG(INFO) << "Receive result for JoinGroupCallQuery: " << to_string(updates); + td_->updates_manager_->on_get_updates(std::move(updates)); + + promise.set_error(Status::Error(500, "Wrong join response received")); +} + +Result> GroupCallManager::get_group_call_join_response_object( + string json_response) { + auto r_value = json_decode(json_response); + if (r_value.is_error()) { + return Status::Error("Can't parse JSON object"); + } + + auto value = r_value.move_as_ok(); + if (value.type() != JsonValue::Type::Object) { + return Status::Error("Expected an Object"); + } + + auto &value_object = value.get_object(); + TRY_RESULT(transport, get_json_object_field(value_object, "transport", JsonValue::Type::Object, false)); + CHECK(transport.type() == JsonValue::Type::Object); + auto &transport_object = transport.get_object(); + + TRY_RESULT(candidates, get_json_object_field(transport_object, "candidates", JsonValue::Type::Array, false)); + TRY_RESULT(fingerprints, get_json_object_field(transport_object, "fingerprints", JsonValue::Type::Array, false)); + TRY_RESULT(ufrag, get_json_object_string_field(transport_object, "ufrag", false)); + TRY_RESULT(pwd, get_json_object_string_field(transport_object, "pwd", false)); + // skip "xmlns", "rtcp-mux" + + vector> fingerprints_object; + for (auto &fingerprint : fingerprints.get_array()) { + if (fingerprint.type() != JsonValue::Type::Object) { + return Status::Error("Expected JSON object as fingerprint"); + } + auto &fingerprint_object = fingerprint.get_object(); + TRY_RESULT(hash, get_json_object_string_field(fingerprint_object, "hash", false)); + TRY_RESULT(setup, get_json_object_string_field(fingerprint_object, "setup", false)); + TRY_RESULT(fingerprint_value, get_json_object_string_field(fingerprint_object, "fingerprint", false)); + fingerprints_object.push_back( + td_api::make_object(hash, setup, fingerprint_value)); + } + + vector> candidates_object; + for (auto &candidate : candidates.get_array()) { + if (candidate.type() != JsonValue::Type::Object) { + return Status::Error("Expected JSON object as candidate"); + } + auto &candidate_object = candidate.get_object(); + TRY_RESULT(port, get_json_object_string_field(candidate_object, "port", false)); + TRY_RESULT(protocol, get_json_object_string_field(candidate_object, "protocol", false)); + TRY_RESULT(network, get_json_object_string_field(candidate_object, "network", false)); + TRY_RESULT(generation, get_json_object_string_field(candidate_object, "generation", false)); + TRY_RESULT(id, get_json_object_string_field(candidate_object, "id", false)); + TRY_RESULT(component, get_json_object_string_field(candidate_object, "component", false)); + TRY_RESULT(foundation, get_json_object_string_field(candidate_object, "foundation", false)); + TRY_RESULT(priority, get_json_object_string_field(candidate_object, "priority", false)); + TRY_RESULT(ip, get_json_object_string_field(candidate_object, "ip", false)); + TRY_RESULT(type, get_json_object_string_field(candidate_object, "type", false)); + TRY_RESULT(tcp_type, get_json_object_string_field(candidate_object, "tcptype")); + TRY_RESULT(rel_addr, get_json_object_string_field(candidate_object, "rel-addr")); + TRY_RESULT(rel_port, get_json_object_string_field(candidate_object, "rel-port")); + candidates_object.push_back(td_api::make_object( + port, protocol, network, generation, id, component, foundation, priority, ip, type, tcp_type, rel_addr, + rel_port)); + } + + auto payload = td_api::make_object(ufrag, pwd, std::move(fingerprints_object)); + return td_api::make_object(std::move(payload), std::move(candidates_object)); +} + +void GroupCallManager::on_join_group_call_response(InputGroupCallId group_call_id, string json_response) { + auto it = pending_join_requests_.find(group_call_id); + if (it == pending_join_requests_.end()) { + return; + } + + auto result = get_group_call_join_response_object(std::move(json_response)); + if (result.is_error()) { + LOG(ERROR) << "Failed to parse join response JSON object: " << result.error().message(); + it->second.promise.set_error(Status::Error(500, "Receive invalid join group call response payload")); + } else { + it->second.promise.set_value(result.move_as_ok()); + } + pending_join_requests_.erase(it); +} + +void GroupCallManager::finish_join_group_call(InputGroupCallId group_call_id, uint64 generation, Status error) { + CHECK(error.is_error()); + auto it = pending_join_requests_.find(group_call_id); + if (it == pending_join_requests_.end() || it->second.generation != generation) { + return; + } + it->second.promise.set_error(std::move(error)); + pending_join_requests_.erase(it); +} + void GroupCallManager::leave_group_call(InputGroupCallId group_call_id, int32 source, Promise &&promise) { td_->create_handler(std::move(promise))->send(group_call_id, source); } @@ -169,6 +377,9 @@ InputGroupCallId GroupCallManager::update_group_call(const tl_object_ptrparticipants_count_; call.version = group_call->version_; + if (group_call->params_ != nullptr) { + on_join_group_call_response(call_id, std::move(group_call->params_->data_)); + } break; } case telegram_api::groupCallDiscarded::ID: { diff --git a/td/telegram/GroupCallManager.h b/td/telegram/GroupCallManager.h index 871306f99..54ec47f33 100644 --- a/td/telegram/GroupCallManager.h +++ b/td/telegram/GroupCallManager.h @@ -26,19 +26,33 @@ class GroupCallManager : public Actor { void create_group_call(ChannelId channel_id, Promise &&promise); + void join_group_call(InputGroupCallId group_call_id, td_api::object_ptr &&payload, + int32 source, bool is_muted, + Promise> &&promise); + void leave_group_call(InputGroupCallId group_call_id, int32 source, Promise &&promise); void discard_group_call(InputGroupCallId group_call_id, Promise &&promise); void on_update_group_call(tl_object_ptr group_call_ptr); + void process_join_group_call_response(InputGroupCallId group_call_id, uint64 generation, + tl_object_ptr &&updates, Promise &&promise); + private: struct GroupCall; void tear_down() override; + void on_join_group_call_response(InputGroupCallId group_call_id, string json_response); + + void finish_join_group_call(InputGroupCallId group_call_id, uint64 generation, Status error); + InputGroupCallId update_group_call(const tl_object_ptr &group_call_ptr); + static Result> get_group_call_join_response_object( + string json_response); + static tl_object_ptr get_update_group_call_object(InputGroupCallId group_call_id, const GroupCall *group_call); @@ -49,6 +63,13 @@ class GroupCallManager : public Actor { ActorShared<> parent_; std::unordered_map, InputGroupCallIdHash> group_calls_; + + struct PendingJoinRequest { + uint64 generation = 0; + Promise> promise; + }; + std::unordered_map pending_join_requests_; + uint64 join_group_request_generation_ = 0; }; } // namespace td diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 44a8b5896..ed57eb6da 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -6041,6 +6041,15 @@ void Td::on_request(uint64 id, const td_api::createChatGroupCall &request) { contacts_manager_->create_channel_group_call(DialogId(request.chat_id_), std::move(query_promise)); } +void Td::on_request(uint64 id, td_api::joinGroupCall &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + TRY_RESULT_PROMISE(promise, group_call_id, InputGroupCallId::from_group_call_id(request.group_call_id_)); + + group_call_manager_->join_group_call(group_call_id, std::move(request.payload_), request.source_, request.is_muted_, + std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::leaveGroupCall &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 6107c8ce1..c36626a48 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -694,6 +694,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::createChatGroupCall &request); + void on_request(uint64 id, td_api::joinGroupCall &request); + void on_request(uint64 id, const td_api::leaveGroupCall &request); void on_request(uint64 id, const td_api::discardGroupCall &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 370ab3e32..1804074c8 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -2836,6 +2836,12 @@ class CliClient final : public Actor { send_request(td_api::make_object(as_call_id(args), "{}")); } else if (op == "ccgc") { send_request(td_api::make_object(as_chat_id(args))); + } else if (op == "jgc") { + vector> fingerprints; + fingerprints.push_back(td_api::make_object("hash", "setup", "fingerprint")); + fingerprints.push_back(td_api::make_object("h2", "s2", "fingerprint2")); + send_request(td_api::make_object( + args, td_api::make_object("ufrag", "pwd", std::move(fingerprints)), 123, true)); } else if (op == "lgc") { send_request(td_api::make_object(args, 123)); } else if (op == "dgc") {