diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index ccb2aeda0..cc30c77a2 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -349,7 +349,7 @@ chatMemberStatusCreator is_member:Bool = ChatMemberStatus; //@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_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 him +//@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 chatMemberStatusAdministrator 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 = ChatMemberStatus; //@description The user is a member of a chat, without any additional privileges or restrictions @@ -679,7 +679,7 @@ keyboardButton text:string type:KeyboardButtonType = KeyboardButton; //@description A button that opens a specified URL @url HTTP or tg:// URL to open inlineKeyboardButtonTypeUrl url:string = InlineKeyboardButtonType; -//@description A button that opens a specified URL and automatically logs in in current user if they allowed to do that @url HTTP URL to open @id Unique button identifier @forward_text If non-empty, new text of the button in forwarded messages +//@description A button that opens a specified URL and automatically logs in in current user if they allowed to do that @url An HTTP URL to open @id Unique button identifier @forward_text If non-empty, new text of the button in forwarded messages inlineKeyboardButtonTypeLoginUrl url:string id:int32 forward_text:string = InlineKeyboardButtonType; //@description A button that sends a special callback query to a bot @data Data to be sent to the bot via a callback query @@ -721,6 +721,16 @@ replyMarkupShowKeyboard rows:vector> resize_keyboard:Bool replyMarkupInlineKeyboard rows:vector> = ReplyMarkup; +//@class LoginUrlInfo @description Contains information about an inline button of type inlineKeyboardButtonTypeLoginUrl returned by the method getLoginUrlInfo + +//@description An HTTP url needs to be open @url The URL to open @skip_confirm True, if there is no need to show an ordinary open URL confirm +loginUrlInfoOpen url:string skip_confirm:Bool = LoginUrlInfo; + +//@description An authorization confirmation dialog needs to be shown to the user @url An HTTP URL to be opened @domain A domain of the URL +//@bot_user_id User identifier of a bot linked with the website @request_write_access True, if the user needs to be requested to give the permission to the bot to send them messages +loginUrlInfoRequestConfirmation url:string domain:string bot_user_id:int32 request_write_access:Bool = LoginUrlInfo; + + //@class RichText @description Describes a text object inside an instant-view web page //@description A plain text @text Text @@ -2122,7 +2132,7 @@ pushMessageContentVoiceNote voice_note:voiceNote is_pinned:Bool = PushMessageCon pushMessageContentBasicGroupChatCreate = PushMessageContent; //@description New chat members were invited to a group @member_name Name of the added member @is_current_user True, if the current user was added to the group -//@is_returned True, if the user has returned to the group himself +//@is_returned True, if the user has returned to the group themself pushMessageContentChatAddMembers member_name:string is_current_user:Bool is_returned:Bool = PushMessageContent; //@description A chat photo was edited @@ -2132,7 +2142,7 @@ pushMessageContentChatChangePhoto = PushMessageContent; pushMessageContentChatChangeTitle title:string = PushMessageContent; //@description A chat member was deleted @member_name Name of the deleted member @is_current_user True, if the current user was deleted from the group -//@is_left True, if the user has left the group himself +//@is_left True, if the user has left the group themself pushMessageContentChatDeleteMember member_name:string is_current_user:Bool is_left:Bool = PushMessageContent; //@description A new member joined the chat by invite link @@ -3248,6 +3258,17 @@ setPollAnswer chat_id:int53 message_id:int53 option_ids:vector = Ok; stopPoll chat_id:int53 message_id:int53 reply_markup:ReplyMarkup = Ok; +//@description Returns information about a button of type inlineKeyboardButtonTypeLoginUrl. The method needs to be called when the user presses the button +//@chat_id Chat identifier of the message with the button @message_id Message identifier of the message with the button @button_id Button identifier +getLoginUrlInfo chat_id:int53 message_id:int53 button_id:int32 = LoginUrlInfo; + +//@description Returns an HTTP URL which can be used to automatically authorize the user on a website after clicking an inline button of type inlineKeyboardButtonTypeLoginUrl. +//-Use the method getLoginUrlInfo to find whether a prior user confirmation is needed. If an error is returned, then the button must be handled as an ordinary URL button +//@chat_id Chat identifier of the message with the button @message_id Message identifier of the message with the button @button_id Button identifier +//@allow_write_access True, if the user allowed the bot to send them messages +getLoginUrl chat_id:int53 message_id:int53 button_id:int32 allow_write_access:Bool = HttpUrl; + + //@description Sends an inline query to a bot and returns its results. Returns an error with code 502 if the bot fails to answer the query before the query timeout expires @bot_user_id The identifier of the target bot //@chat_id Identifier of the chat, where the query was sent @user_location Location of the user, only if needed @query Text of the query @offset Offset of the first entry to return getInlineQueryResults bot_user_id:int32 chat_id:int53 user_location:location query:string offset:string = InlineQueryResults; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index 75340a52f..2a50a7842 100644 Binary files a/td/generate/scheme/td_api.tlo and b/td/generate/scheme/td_api.tlo differ diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index eba4c3410..252fef1f1 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -3394,7 +3394,6 @@ class GetStatsUrlQuery : public Td::ResultHandler { } void send(DialogId dialog_id, const string ¶meters, bool is_dark) { - // TODO use parameters and is_dark dialog_id_ = dialog_id; auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); @@ -3422,6 +3421,120 @@ class GetStatsUrlQuery : public Td::ResultHandler { } }; +class RequestUrlAuthQuery : public Td::ResultHandler { + Promise> promise_; + string url_; + DialogId dialog_id_; + + public: + explicit RequestUrlAuthQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(string url, DialogId dialog_id, MessageId message_id, int32 button_id) { + url_ = std::move(url); + dialog_id_ = 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(create_storer(telegram_api::messages_requestUrlAuth( + std::move(input_peer), message_id.get_server_message_id().get(), button_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()); + } + + auto result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive " << to_string(result); + switch (result->get_id()) { + case telegram_api::urlAuthResultRequest::ID: { + auto request = telegram_api::move_object_as(result); + UserId bot_user_id = ContactsManager::get_user_id(request->bot_); + if (!bot_user_id.is_valid()) { + return on_error(id, Status::Error(500, "Receive invalid bot_user_id")); + } + td->contacts_manager_->on_get_user(std::move(request->bot_), "RequestUrlAuthQuery"); + bool request_write_access = + (request->flags_ & telegram_api::urlAuthResultRequest::REQUEST_WRITE_ACCESS_MASK) != 0; + promise_.set_value(td_api::make_object( + url_, request->domain_, td->contacts_manager_->get_user_id_object(bot_user_id, "RequestUrlAuthQuery"), + request_write_access)); + break; + } + case telegram_api::urlAuthResultAccepted::ID: { + auto accepted = telegram_api::move_object_as(result); + promise_.set_value(td_api::make_object(accepted->url_, true)); + break; + } + case telegram_api::urlAuthResultDefault::ID: + promise_.set_value(td_api::make_object(url_, false)); + break; + } + } + + void on_error(uint64 id, Status status) override { + if (!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "RequestUrlAuthQuery")) { + LOG(INFO) << "RequestUrlAuthQuery returned " << status; + } + promise_.set_value(td_api::make_object(url_, false)); + } +}; + +class AcceptUrlAuthQuery : public Td::ResultHandler { + Promise> promise_; + string url_; + DialogId dialog_id_; + + public: + explicit AcceptUrlAuthQuery(Promise> &&promise) : promise_(std::move(promise)) { + } + + void send(string url, DialogId dialog_id, MessageId message_id, int32 button_id, bool allow_write_access) { + url_ = std::move(url); + dialog_id_ = dialog_id; + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); + CHECK(input_peer != nullptr); + int32 flags = 0; + if (allow_write_access) { + flags |= telegram_api::messages_acceptUrlAuth::WRITE_ALLOWED_MASK; + } + send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_acceptUrlAuth( + flags, false /*ignored*/, std::move(input_peer), message_id.get_server_message_id().get(), button_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()); + } + + auto result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive " << to_string(result); + switch (result->get_id()) { + case telegram_api::urlAuthResultRequest::ID: + LOG(ERROR) << "Receive unexpected " << to_string(result); + return on_error(id, Status::Error(500, "Receive unexpected urlAuthResultRequest")); + case telegram_api::urlAuthResultAccepted::ID: { + auto accepted = telegram_api::move_object_as(result); + promise_.set_value(td_api::make_object(accepted->url_)); + break; + } + case telegram_api::urlAuthResultDefault::ID: + promise_.set_value(td_api::make_object(url_)); + break; + } + } + + void on_error(uint64 id, Status status) override { + if (!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "AcceptUrlAuthQuery")) { + LOG(INFO) << "AcceptUrlAuthQuery returned " << status; + } + promise_.set_error(std::move(status)); + } +}; + class GetChannelDifferenceQuery : public Td::ResultHandler { DialogId dialog_id_; int32 pts_; @@ -6523,6 +6636,64 @@ void MessagesManager::get_dialog_statistics_url(DialogId dialog_id, const string td_->create_handler(std::move(promise))->send(dialog_id, parameters, is_dark); } +Result MessagesManager::get_login_button_url(DialogId dialog_id, MessageId message_id, int32 button_id) { + Dialog *d = get_dialog_force(dialog_id); + if (d == nullptr) { + return Status::Error(3, "Chat not found"); + } + if (!have_input_peer(dialog_id, AccessRights::Read)) { + return Status::Error(3, "Can't access the chat"); + } + + auto m = get_message_force(d, message_id, "get_login_button_url"); + if (m == nullptr) { + return Status::Error(5, "Message not found"); + } + if (m->reply_markup == nullptr || m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) { + return Status::Error(5, "Message has no inline keyboard"); + } + if (!message_id.is_server()) { + // it shouldn't have UrlAuth buttons anyway + return Status::Error(5, "Message is not server"); + } + if (dialog_id.get_type() == DialogType::SecretChat) { + // secret chat messages can't have reply markup, so this shouldn't happen now + return Status::Error(5, "Message is in a secret chat"); + } + + for (auto &row : m->reply_markup->inline_keyboard) { + for (auto &button : row) { + if (button.type == InlineKeyboardButton::Type::UrlAuth && button.id == button_id) { + return button.data; + } + } + } + + return Status::Error(5, "Button not found"); +} + +void MessagesManager::get_login_url_info(DialogId dialog_id, MessageId message_id, int32 button_id, + Promise> &&promise) { + auto r_url = get_login_button_url(dialog_id, message_id, button_id); + if (r_url.is_error()) { + return promise.set_error(r_url.move_as_error()); + } + + td_->create_handler(std::move(promise)) + ->send(r_url.move_as_ok(), dialog_id, message_id, button_id); +} + +void MessagesManager::get_login_url(DialogId dialog_id, MessageId message_id, int32 button_id, bool allow_write_access, + Promise> &&promise) { + auto r_url = get_login_button_url(dialog_id, message_id, button_id); + if (r_url.is_error()) { + return promise.set_error(r_url.move_as_error()); + } + + td_->create_handler(std::move(promise)) + ->send(r_url.move_as_ok(), dialog_id, message_id, button_id, allow_write_access); +} + void MessagesManager::load_secret_thumbnail(FileId thumbnail_file_id) { class Callback : public FileManager::DownloadCallback { public: diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 86223c84f..9904d9673 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -665,10 +665,16 @@ class MessagesManager : public Actor { void report_dialog(DialogId dialog_id, const tl_object_ptr &reason, const vector &message_ids, Promise &&promise); + void on_get_peer_settings(DialogId dialog_id, tl_object_ptr &&peer_settings); + void get_dialog_statistics_url(DialogId dialog_id, const string ¶meters, bool is_dark, Promise> &&promise); - void on_get_peer_settings(DialogId dialog_id, tl_object_ptr &&peer_settings); + void get_login_url_info(DialogId dialog_id, MessageId message_id, int32 button_id, + Promise> &&promise); + + void get_login_url(DialogId dialog_id, MessageId message_id, int32 button_id, bool allow_write_access, + Promise> &&promise); void before_get_difference(); @@ -2263,6 +2269,8 @@ class MessagesManager : public Actor { void suffix_load_till_date(Dialog *d, int32 date, Promise<> promise); void suffix_load_till_message_id(Dialog *d, MessageId message_id, Promise<> promise); + Result get_login_button_url(DialogId dialog_id, MessageId message_id, int32 button_id); + Result get_invoice_message_id(FullMessageId full_message_id); bool is_broadcast_channel(DialogId dialog_id) const; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index d0370fbeb..3b7ffd174 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -7054,6 +7054,20 @@ void Td::on_request(uint64 id, td_api::stopPoll &request) { std::move(request.reply_markup_), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getLoginUrlInfo &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + messages_manager_->get_login_url_info(DialogId(request.chat_id_), MessageId(request.message_id_), request.button_id_, + std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getLoginUrl &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + messages_manager_->get_login_url(DialogId(request.chat_id_), MessageId(request.message_id_), request.button_id_, + request.allow_write_access_, std::move(promise)); +} + void Td::on_request(uint64 id, td_api::getInlineQueryResults &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.query_); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 55ca9f895..da9541fa2 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -874,6 +874,10 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, td_api::stopPoll &request); + void on_request(uint64 id, const td_api::getLoginUrlInfo &request); + + void on_request(uint64 id, const td_api::getLoginUrl &request); + void on_request(uint64 id, td_api::getInlineQueryResults &request); void on_request(uint64 id, td_api::answerInlineQuery &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 47dd9c325..cf39f5877 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -475,6 +475,10 @@ class CliClient final : public Actor { return transform(full_split(trim(message_ids), delimiter), as_message_id); } + static int32 as_button_id(Slice str) { + return to_integer(trim(str)); + } + int32 as_user_id(Slice str) const { str = trim(str); if (str[0] == '@') { @@ -3637,6 +3641,20 @@ class CliClient final : public Actor { std::tie(parameters, is_dark) = split(args); send_request(td_api::make_object(as_chat_id(args), parameters, as_bool(is_dark))); + } else if (op == "glui" || op == "glu" || op == "glua") { + string chat_id; + string message_id; + string button_id; + std::tie(chat_id, args) = split(args); + std::tie(message_id, button_id) = split(args); + + if (op == "glui") { + send_request(td_api::make_object(as_chat_id(chat_id), as_message_id(message_id), + as_button_id(button_id))); + } else { + send_request(td_api::make_object(as_chat_id(chat_id), as_message_id(message_id), + as_button_id(button_id), op == "glua")); + } } else if (op == "rsgs" || op == "rchs") { string supergroup_id; string user_id;