
Criando um Expert Advisor Integrado ao Telegram em MQL5 (Parte 6): Adicionando Botões Inline Interativos
Introdução
Este artigo explora como tornar nosso MetaQuotes Language 5 (MQL5) Expert Advisor mais responsivo e interativo para os usuários por meio do Telegram. Na quinta parte desta série, lançamos as bases do nosso bot implementando a capacidade de responder a comandos e mensagens do Telegram e criando botões de teclado personalizados. Neste segmento, vamos elevar a interatividade do nosso bot ao integrar botões inline que disparam várias ações e respondem dinamicamente às entradas do usuário.
O artigo está organizado para abordar alguns componentes principais. Primeiro, vamos apresentar os botões inline em bots do Telegram, incluindo o que são, sua utilidade e os benefícios que oferecem em relação a outros métodos de criação de interfaces para bots. Em seguida, discutiremos como utilizar esses botões inline em MQL5, para que possam fazer parte da interface do usuário do nosso Expert Advisor.
A partir daí, vamos demonstrar como lidar com as consultas de callback enviadas pelo Telegram quando um usuário pressiona um botão. Isso envolverá o processamento da ação do usuário e a determinação do próximo passo apropriado que o bot deve tomar em sua conversa com o usuário. Por fim, testaremos a funcionalidade integrada do bot para garantir que tudo funcione perfeitamente. Aqui estão os tópicos que abordaremos no artigo:
- Introdução aos Botões Inline em Bots do Telegram
- Integração de Botões Inline no MQL5
- Tratamento de Consultas de Callback para Ações dos Botões
- Testando a Implementação dos Estados dos Botões Inline
- Conclusão
Ao final deste artigo, você terá uma compreensão clara de como integrar e gerenciar botões inline dentro do seu Expert Advisor em MQL5 para o Telegram, aprimorando a funcionalidade do seu robô de negociação e tornando-o mais responsivo e interativo para os usuários. Vamos começar então.
Introdução aos Botões Inline em Bots do Telegram
Botões inline são elementos interativos que aparecem diretamente dentro das mensagens de bots no Telegram, permitindo que os usuários executem ações com um único toque. Esses botões utilizam marcação de teclado inline formatada em JavaScript Object Notation (JSON) para definir sua aparência e comportamento, oferecendo uma interface mais integrada e responsiva em comparação com métodos tradicionais. Ao incorporar esses botões diretamente nas mensagens, os bots podem oferecer aos usuários uma experiência simplificada e interativa imediata, sem exigir comandos ou mensagens de texto adicionais. Para ilustrar exatamente do que estamos falando, apresentamos abaixo uma ilustração visual dos botões inline:
A principal vantagem dos botões inline sobre os teclados de resposta tradicionais está na sua capacidade de permanecer dentro da própria mensagem, tornando as interações mais contínuas e contextualmente relevantes. Os botões inline, definidos usando estruturas JSON, permitem interações complexas com o usuário e respostas dinâmicas. Essa abordagem elimina a necessidade de menus separados ou mensagens adicionais, reduzindo a desordem e aumentando o engajamento do usuário ao oferecer feedback e ações instantâneos. Com esse entendimento, podemos agora iniciar a implementação deles em MQL5 para o MetaTrader 5, conforme a próxima seção.
Integração de Botões Inline no MQL5
Incorporar botões inline em nosso Expert Advisor (EA) em MQL5 requer uma estrutura capaz de lidar com as interações de mensagens e os diversos estados dos botões. Isso é alcançado ao criar e gerenciar várias classes, cada uma responsável por uma parte diferente do processamento de chamadas e mensagens. Explicaremos a integração em detalhes, incluindo as classes e funções utilizadas, e, mais importante, por que elas são usadas e como contribuem para o funcionamento do bot com botões inline. A primeira coisa que precisamos fazer é projetar uma classe que possa encapsular todos os detalhes de uma mensagem recebida do Telegram.
//+------------------------------------------------------------------+ //| Class_Message | //+------------------------------------------------------------------+ class Class_Message : public CObject { public: Class_Message(); // Constructor ~Class_Message(){}; // Destructor bool done; //--- Indicates if a message has been processed. long update_id; //--- Stores the update ID from Telegram. long message_id; //--- Stores the message ID. long from_id; //--- Stores the sender’s ID. string from_first_name; //--- Stores the sender’s first name. string from_last_name; //--- Stores the sender’s last name. string from_username; //--- Stores the sender’s username. long chat_id; //--- Stores the chat ID. string chat_first_name; //--- Stores the chat’s first name. string chat_last_name; //--- Stores the chat’s last name. string chat_username; //--- Stores the chat’s username. string chat_type; //--- Stores the chat type. datetime message_date; //--- Stores the date of the message. string message_text; //--- Stores the text of the message. };
Aqui, definimos a classe "Class_Message", que atua como um contêiner para todos os detalhes relevantes sobre mensagens recebidas do Telegram. Essa classe é essencial para gerenciar e processar os dados das mensagens dentro do nosso Expert Advisor (EA) em MQL5.
Nesta classe, incluímos vários atributos públicos que capturam aspectos específicos de uma mensagem. O atributo "done" indica se a mensagem já foi processada. Os atributos "update_id" e "message_id" armazenam identificadores únicos para a atualização e para a mensagem, respectivamente. Os atributos "from_id", "from_first_name", "from_last_name" e "from_username" contêm informações sobre o remetente da mensagem. Da mesma forma, "chat_id", "chat_first_name", "chat_last_name", "chat_username" e "chat_type" capturam detalhes sobre o chat onde a mensagem foi enviada. O atributo "message_date" registra a data e hora da mensagem, e "message_text" armazena o conteúdo real da mensagem. Após definir os membros da classe, podemos então prosseguir para inicializar seus valores.
//+------------------------------------------------------------------+ //| Constructor to initialize class members | //+------------------------------------------------------------------+ Class_Message::Class_Message(void) { done = false; //--- Sets default value indicating message not yet processed. update_id = 0; //--- Initializes update ID to zero. message_id = 0; //--- Initializes message ID to zero. from_id = 0; //--- Initializes sender ID to zero. from_first_name = NULL; //--- Sets sender's first name to NULL (empty). from_last_name = NULL; //--- Sets sender's last name to NULL (empty). from_username = NULL; //--- Sets sender's username to NULL (empty). chat_id = 0; //--- Initializes chat ID to zero. chat_first_name = NULL; //--- Sets chat’s first name to NULL (empty). chat_last_name = NULL; //--- Sets chat’s last name to NULL (empty). chat_username = NULL; //--- Sets chat’s username to NULL (empty). chat_type = NULL; //--- Sets chat type to NULL (empty). message_date = 0; //--- Initializes message date to zero. message_text = NULL; //--- Sets message text to NULL (empty). }
Aqui, inicializamos o construtor da classe "Class_Message" para definir valores padrão para todos os atributos da classe. O atributo "done" é definido como falso para indicar que a mensagem ainda não foi processada. Inicializamos "update_id", "message_id", "from_id" e "chat_id" com 0, enquanto "from_first_name", "from_last_name", "from_username", "chat_first_name", "chat_last_name", "chat_username" e "chat_type" são definidos como NULL para indicar que esses campos estão vazios. Por fim, "message_date" é definido como 0 e "message_text" é inicializado como NULL, garantindo que cada nova instância de "Class_Message" comece com valores padrão antes de ser preenchida com os dados reais. Seguindo a mesma lógica, definimos a classe de chat onde armazenaremos os detalhes de atualização do chat conforme abaixo:
//+------------------------------------------------------------------+ //| Class_Chat | //+------------------------------------------------------------------+ class Class_Chat : public CObject { public: Class_Chat(){}; //--- Declares an empty constructor. ~Class_Chat(){}; //--- Declares an empty destructor. long member_id; //--- Stores the chat ID. int member_state; //--- Stores the state of the chat. datetime member_time; //--- Stores the time of chat activities. Class_Message member_last; //--- Instance of Class_Message to store the last message. Class_Message member_new_one; //--- Instance of Class_Message to store the new message. };
Depois de definir a classe de chats, precisaremos definir uma classe extra que irá lidar com as consultas de callback. Ela será essencial para tratar os dados específicos associados às consultas de callback, que diferem das mensagens regulares. As consultas de callback fornecem dados exclusivos, como os dados de retorno e a interação que acionou a consulta, os quais não estão presentes em mensagens comuns. Assim, essa classe nos permitirá capturar e gerenciar esses dados especializados de forma eficaz. Além disso, ela permitirá lidar com interações do usuário com botões inline de maneira distinta. Essa separação garantirá que possamos processar e responder com precisão aos cliques em botões, distinguindo-os de outros tipos de mensagens e interações. A implementação será a seguinte:
//+------------------------------------------------------------------+ //| Class_CallbackQuery | //+------------------------------------------------------------------+ class Class_CallbackQuery : public CObject { public: string id; //--- Stores the callback query ID. long from_id; //--- Stores the sender’s ID. string from_first_name; //--- Stores the sender’s first name. string from_last_name; //--- Stores the sender’s last name. string from_username; //--- Stores the sender’s username. long message_id; //--- Stores the message ID related to the callback. string message_text; //--- Stores the message text. string data; //--- Stores the callback data. long chat_id; //--- Stores the chat ID to send responses. };
Aqui, definimos uma classe chamada "Class_CallbackQuery" para gerenciar os dados associados às consultas de callback do Telegram. Essa classe é crucial para lidar com interações com botões inline. Na classe, declaramos várias variáveis para armazenar informações específicas das consultas de callback. A variável "id" contém o identificador único da consulta de callback, permitindo distinguir entre diferentes consultas. "from_id" armazena o ID do remetente, o que ajuda a identificar o usuário que acionou a consulta. Utilizamos "from_first_name", "from_last_name" e "from_username" para acompanhar os dados do nome do remetente.
A variável "message_id" captura o ID da mensagem relacionada ao callback, enquanto "message_text" contém o texto dessa mensagem. "data" contém os dados de callback enviados com o botão inline, o que é crucial para determinar a ação a ser executada com base no botão pressionado. Por fim, "chat_id" armazena o ID do chat para onde as respostas devem ser enviadas, garantindo que a resposta chegue ao contexto correto. O restante da definição e inicialização da classe do Expert permanece o mesmo, exceto que agora precisamos incluir uma função personalizada adicional para processar consultas de callback.
//+------------------------------------------------------------------+ //| Class_Bot_EA | //+------------------------------------------------------------------+ class Class_Bot_EA { private: string member_token; //--- Stores the bot’s token. string member_name; //--- Stores the bot’s name. long member_update_id; //--- Stores the last update ID processed by the bot. CArrayString member_users_filter; //--- Array to filter messages from specific users. bool member_first_remove; //--- Indicates if the first message should be removed. protected: CList member_chats; //--- List to store chat objects. public: Class_Bot_EA(); //--- Constructor. ~Class_Bot_EA(){}; //--- Destructor. int getChatUpdates(); //--- Function to get updates from Telegram. void ProcessMessages(); //--- Function to process incoming messages. void ProcessCallbackQuery(Class_CallbackQuery &cb_query); //--- Function to process callback queries. }; //+------------------------------------------------------------------+ //| Constructor for Class_Bot_EA | //+------------------------------------------------------------------+ Class_Bot_EA::Class_Bot_EA(void) { member_token = NULL; //--- Initialize bot token to NULL (empty). member_token = getTrimmedToken(InpToken); //--- Assign bot token by trimming input token. member_name = NULL; //--- Initialize bot name to NULL. member_update_id = 0; //--- Initialize last update ID to zero. member_first_remove = true; //--- Set first remove flag to true. member_chats.Clear(); //--- Clear the list of chat objects. member_users_filter.Clear(); //--- Clear the user filter array. }
Depois de definir todas as classes necessárias, podemos prosseguir para obter os detalhes de atualização do chat.
//+------------------------------------------------------------------+ //| Function to get chat updates from Telegram | //+------------------------------------------------------------------+ int Class_Bot_EA::getChatUpdates(void) { //... return 0; //--- Return 0 to indicate successful processing of updates. }
Aqui, definimos a função "getChatUpdates" como um método da classe "Class_Bot_EA". Essa função tem como objetivo obter atualizações da API do Telegram: atualizações que consistem em novas mensagens ou consultas de callback que o bot ainda não processou. A implementação atual de "getChatUpdates" retorna um valor inteiro igual a 0, o que convencionalmente significa que a operação foi concluída com sucesso. Ao retornar 0, sinalizamos que capturamos as atualizações e as processamos sem problemas. O próximo passo é preencher essa função para que ela cumpra seu propósito: buscar atualizações na API.
if (member_token == NULL) { //--- If bot token is empty Print("ERR: TOKEN EMPTY"); //--- Print error message indicating empty token. return (-1); //--- Return -1 to indicate error. }
Verificamos se a variável "member_token" está vazia. Se "member_token" for NULL, isso significa que não recebemos um token de bot. Portanto, informamos o usuário imprimindo "ERR: TOKEN EMPTY" que uma informação essencial não foi fornecida, e retornamos -1 para sinalizar uma condição de erro que interrompe a função. Se passarmos dessa etapa, podemos prosseguir com o envio da requisição para obter as atualizações do chat.
string out; //--- String to hold response data. string url = TELEGRAM_BASE_URL + "/bot" + member_token + "/getUpdates"; //--- Construct URL for Telegram API. string params = "offset=" + IntegerToString(member_update_id); //--- Set parameters including the offset based on the last update ID. int res = postRequest(out, url, params, WEB_TIMEOUT); //--- Send a POST request to Telegram with a timeout.
Primeiramente, definimos uma variável chamada "out" do tipo string. Configuramos essa variável "out" para armazenar os dados de resposta que receberemos da API do Telegram. Em seguida, construímos a URL da API necessária para obter as atualizações. Para isso, combinamos "TELEGRAM_BASE_URL" com vários outros componentes: "/bot" e o token do bot, que está armazenado em "member_token"; e "/getUpdates", que é o endpoint que usamos para obter as atualizações do Telegram. A API do Telegram é parte da principal plataforma que nosso aplicativo utiliza, e o método "getUpdates" é como buscamos novos dados dessa plataforma. Então, fazemos a chamada para a API e permitimos que ela retorne novos dados para nossa aplicação. Com a resposta em mãos, podemos usá-la para realizar outras alterações.
if (res == 0) { //--- If request succeeds (res = 0) CJSONValue obj_json(NULL, jv_UNDEF); //--- Create a JSON object to parse the response. bool done = obj_json.Deserialize(out); //--- Deserialize the response. if (!done) { //--- If deserialization fails Print("ERR: JSON PARSING"); //--- Print error message indicating JSON parsing error. return (-1); //--- Return -1 to indicate error. } bool ok = obj_json["ok"].ToBool(); //--- Check if the response has "ok" field set to true. if (!ok) { //--- If "ok" field is false Print("ERR: JSON NOT OK"); //--- Print error message indicating that JSON response is not okay. return (-1); //--- Return -1 to indicate error. } }
Começamos determinando se a requisição foi bem-sucedida verificando o valor da variável "res". Se "res" for igual a 0, sabemos que a requisição foi bem-sucedida, e podemos prosseguir para lidar com a resposta. Criamos um objeto "CJSONValue", chamado "obj_json", que utilizamos para analisar a resposta. O objeto é inicializado no estado "NULL" e com "jv_UNDEF", o que denota um estado indefinido ou um objeto preparado para receber dados. Após a análise com "out", temos um objeto que contém os dados analisados ou encontramos um erro durante a análise.
Se a desserialização falhar — o que é indicado pela variável chamada "done" sendo falsa — imprimimos a mensagem de erro "ERR: JSON PARSING" e retornamos "-1" para sinalizar um problema. Se conseguirmos desserializar os dados com sucesso, verificamos se a resposta contém um campo chamado "ok". Convertêmo-lo para booleano usando o método "ToBool", e armazenamos o resultado na variável "ok". Se "ok" for falso — ou seja, a requisição não foi bem-sucedida do lado do servidor — imprimimos "ERR: JSON NOT OK" e retornamos "-1". Dessa forma, garantimos que tratamos corretamente tanto a desserialização da resposta quanto seu conteúdo. Em seguida, continuamos a iterar sobre cada resposta utilizando a seguinte lógica.
int total = ArraySize(obj_json["result"].m_elements); //--- Get the total number of update elements. for (int i = 0; i < total; i++) { //--- Iterate through each update element. CJSONValue obj_item = obj_json["result"].m_elements[i]; //--- Access individual update element. if (obj_item["message"].m_type != jv_UNDEF) { //--- Check if the update has a message. Class_Message obj_msg; //--- Create an instance of Class_Message to store the message details. obj_msg.update_id = obj_item["update_id"].ToInt(); //--- Extract and store update ID. obj_msg.message_id = obj_item["message"]["message_id"].ToInt(); //--- Extract and store message ID. obj_msg.message_date = (datetime)obj_item["message"]["date"].ToInt(); //--- Extract and store message date. obj_msg.message_text = obj_item["message"]["text"].ToStr(); //--- Extract and store message text. obj_msg.message_text = decodeStringCharacters(obj_msg.message_text); //--- Decode any special characters in the message text. } }
Para iniciar nossa análise do número total de elementos de atualização na resposta, utilizamos a função ArraySize para contar os elementos dentro do array "m_elements" do objeto "result" em "obj_json". Armazenamos a contagem na variável "total". Em seguida, configuramos um loop que processa repetidamente cada elemento de atualização do array "m_elements". Há "total" elementos a serem processados; portanto, a variável de controle do loop varia de 0 até "total" menos 1. Durante cada iteração, o valor atual da variável de controle "i" indica qual elemento do array "m_elements" estamos acessando. Atribuímos o elemento de índice "i" à variável "obj_item". Agora verificamos se a atualização atual (o "obj_item") contém uma "message" válida.
A seguir, instanciamos um objeto da "Class_Message" chamado "obj_msg", que conterá os detalhes da mensagem em questão. O primeiro campo que preenchemos em "obj_msg" é o campo "update_id". Para isso, extraímos o "update_id" de "obj_item", convertendo-o em inteiro e colocando em "obj_msg.update_id". O próximo campo acessado em "obj_msg" é o campo "message_id". Para esse valor, novamente extraímos as informações do campo "message" de "obj_item". Convertendo o valor no campo "message_id" de "obj_item" para inteiro e o atribuímos a "obj_msg.message_id". Depois disso, preenchemos o campo "datetime" de "obj_msg" com o valor "date" do "item". Em seguida, preenchemos o campo "message_text" em "obj_msg". Extraímos o valor "text" de "message", o convertemos para string e o colocamos em "obj_msg.message_text". Por fim, usamos a função "decodeStringCharacters" para garantir que quaisquer caracteres especiais no "message_text" sejam renderizados corretamente. Uma abordagem semelhante é usada para obter os outros detalhes da resposta.
obj_msg.from_id = obj_item["message"]["from"]["id"].ToInt(); //--- Extract and store the sender's ID. obj_msg.from_first_name = obj_item["message"]["from"]["first_name"].ToStr(); //--- Extract and store the sender's first name. obj_msg.from_first_name = decodeStringCharacters(obj_msg.from_first_name); //--- Decode any special characters in the sender's first name. obj_msg.from_last_name = obj_item["message"]["from"]["last_name"].ToStr(); //--- Extract and store the sender's last name. obj_msg.from_last_name = decodeStringCharacters(obj_msg.from_last_name); //--- Decode any special characters in the sender's last name. obj_msg.from_username = obj_item["message"]["from"]["username"].ToStr(); //--- Extract and store the sender's username. obj_msg.from_username = decodeStringCharacters(obj_msg.from_username); //--- Decode any special characters in the sender's username. obj_msg.chat_id = obj_item["message"]["chat"]["id"].ToInt(); //--- Extract and store the chat ID. obj_msg.chat_first_name = obj_item["message"]["chat"]["first_name"].ToStr(); //--- Extract and store the chat's first name. obj_msg.chat_first_name = decodeStringCharacters(obj_msg.chat_first_name); //--- Decode any special characters in the chat's first name. obj_msg.chat_last_name = obj_item["message"]["chat"]["last_name"].ToStr(); //--- Extract and store the chat's last name. obj_msg.chat_last_name = decodeStringCharacters(obj_msg.chat_last_name); //--- Decode any special characters in the chat's last name. obj_msg.chat_username = obj_item["message"]["chat"]["username"].ToStr(); //--- Extract and store the chat's username. obj_msg.chat_username = decodeStringCharacters(obj_msg.chat_username); //--- Decode any special characters in the chat's username. obj_msg.chat_type = obj_item["message"]["chat"]["type"].ToStr(); //--- Extract and store the chat type.
Após obter os detalhes do chat, prosseguimos para processar a mensagem com base no ID de chat associado.
//--- Process the message based on chat ID. member_update_id = obj_msg.update_id + 1; //--- Update the last processed update ID.
Depois de extrair e armazenar os detalhes necessários da mensagem, atualizamos o último ID de atualização processado. Fazemos isso atribuindo o valor de "obj_msg.update_id" mais 1 à variável "member_update_id". Isso garante que, da próxima vez que processarmos atualizações, possamos pular esta e continuar a partir da próxima. Por fim, precisamos aplicar um filtro às mensagens do usuário.
//--- Check if we need to filter messages based on user or if no filter is applied. if (member_users_filter.Total() == 0 || (member_users_filter.Total() > 0 && member_users_filter.SearchLinear(obj_msg.from_username) >= 0)) { int index = -1; //--- Initialize index to -1 (indicating no chat found). for (int j = 0; j < member_chats.Total(); j++) { //--- Iterate through all chat objects. Class_Chat *chat = member_chats.GetNodeAtIndex(j); //--- Get chat object by index. if (chat.member_id == obj_msg.chat_id) { //--- If chat ID matches index = j; //--- Store the index. break; //--- Break the loop since we found the chat. } } if (index == -1) { //--- If no matching chat was found member_chats.Add(new Class_Chat); //--- Create a new chat object and add it to the list. Class_Chat *chat = member_chats.GetLastNode(); //--- Get the last (newly added) chat object. chat.member_id = obj_msg.chat_id; //--- Assign the chat ID. chat.member_time = TimeLocal(); //--- Record the current time for the chat. chat.member_state = 0; //--- Initialize the chat state to 0. chat.member_new_one.message_text = obj_msg.message_text; //--- Store the new message in the chat. chat.member_new_one.done = false; //--- Mark the new message as not processed. } else { //--- If matching chat was found Class_Chat *chat = member_chats.GetNodeAtIndex(index); //--- Get the chat object by index. chat.member_time = TimeLocal(); //--- Update the time for the chat. chat.member_new_one.message_text = obj_msg.message_text; //--- Store the new message. chat.member_new_one.done = false; //--- Mark the new message as not processed. } }
Para filtrar mensagens com base no usuário ou permitir que todas passem sem filtragem, primeiro verificamos se "member_users_filter" contém algum elemento. Se o filtro estiver vazio ("Total == 0"), deixamos todas as mensagens passarem. Se o filtro contiver elementos ("Total > 0"), verificamos se o nome de usuário do remetente ("obj_msg.from_username") está presente no filtro. Usamos um método de busca sequencial, "SearchLinear", no qual o nome de usuário do remetente é verificado no filtro para ver se está presente. Se o nome de usuário for encontrado (o método retorna um índice 0 ou maior), prosseguimos com o processamento da mensagem normalmente. Após esta etapa de filtragem, buscamos o chat da mensagem. Buscamos o nome de usuário do remetente no filtro para que apenas certos nomes de usuário (aqueles listados no filtro) possam passar.
Quando o "chat.member_id" for igual ao ID do chat da mensagem ("obj_msg.chat_id"), primeiro registramos o índice atual na variável "index" durante o loop e, em seguida, saímos do loop, pois localizamos o chat correto Quando não encontramos correspondências para o chat e "index" permanece -1, criamos um novo objeto de chat e o adicionamos ao "member_chats" usando o método "Add". "GetLastNode" nos ajuda a recuperar o novo objeto de chat, que armazenamos no ponteiro pointer "chat". Atribuímos o ID do chat de "obj_msg.chat_id" a "chat.member_id" e utilizamos a função TimeLocal para registrar o horário atual em "chat.member_time". Definimos o "member_state" do chat inicialmente como 0 e armazenamos a nova mensagem em "chat.member_new_one.message_text".
Também indicamos que a mensagem está não processada definindo "chat.member_new_one.done" como falso. Se encontrarmos um chat correspondente (ou seja, "index" não for -1), recuperamos o objeto de chat correspondente com "GetNodeAtIndex" e atualizamos seu "member_time" com o horário atual. Em seguida, armazenamos a nova mensagem em "chat.member_new_one.message_text" e novamente marcamos como não processada, definindo "chat.member_new_one.done" como falso. Isso garante que o chat seja atualizado com a mensagem mais recente e que o sistema esteja ciente de que a mensagem ainda não foi processada. Em seguida, precisamos lidar com as "callback queries" dos chats do Telegram.
//--- Handle callback queries from Telegram. if (obj_item["callback_query"].m_type != jv_UNDEF) { //--- Check if there is a callback query in the update. Class_CallbackQuery obj_cb_query; //--- Create an instance of Class_CallbackQuery. //... }
Começamos verificando se a atualização atual ("obj_item") contém uma callback query, verificando se o tipo do campo "callback_query" ("m_type") é diferente de "jv_UNDEF". Isso garante que uma callback query esteja presente na atualização. Se essa condição for satisfeita, prosseguimos criando uma instância do objeto "Class_CallbackQuery", chamado "obj_cb_query". Esse objeto será usado para armazenar e gerenciar os detalhes da callback query. Podemos então usar o objeto para obter e armazenar os dados da callback query.
obj_cb_query.id = obj_item["callback_query"]["id"].ToStr(); //--- Extract and store the callback query ID. obj_cb_query.from_id = obj_item["callback_query"]["from"]["id"].ToInt(); //--- Extract and store the sender's ID. obj_cb_query.from_first_name = obj_item["callback_query"]["from"]["first_name"].ToStr(); //--- Extract and store the sender's first name. obj_cb_query.from_first_name = decodeStringCharacters(obj_cb_query.from_first_name); //--- Decode any special characters in the sender's first name. obj_cb_query.from_last_name = obj_item["callback_query"]["from"]["last_name"].ToStr(); //--- Extract and store the sender's last name. obj_cb_query.from_last_name = decodeStringCharacters(obj_cb_query.from_last_name); //--- Decode any special characters in the sender's last name. obj_cb_query.from_username = obj_item["callback_query"]["from"]["username"].ToStr(); //--- Extract and store the sender's username. obj_cb_query.from_username = decodeStringCharacters(obj_cb_query.from_username); //--- Decode any special characters in the sender's username. obj_cb_query.message_id = obj_item["callback_query"]["message"]["message_id"].ToInt(); //--- Extract and store the message ID related to the callback. obj_cb_query.message_text = obj_item["callback_query"]["message"]["text"].ToStr(); //--- Extract and store the message text related to the callback. obj_cb_query.message_text = decodeStringCharacters(obj_cb_query.message_text); //--- Decode any special characters in the message text. obj_cb_query.data = obj_item["callback_query"]["data"].ToStr(); //--- Extract and store the callback data. obj_cb_query.data = decodeStringCharacters(obj_cb_query.data); //--- Decode any special characters in the callback data. obj_cb_query.chat_id = obj_item["callback_query"]["message"]["chat"]["id"].ToInt(); //--- Extract and store the chat ID.
Começamos com os detalhes da callback query propriamente dita. O ID da callback query é obtido a partir do campo "callback_query". Usamos o método "ToStr" para convertê-lo em formato de string, e armazenamos em "obj_cb_query.id". A próxima informação que extraímos é o ID do remetente, retirado do campo "from". Novamente, usamos o método "ToInt", e armazenamos o número convertido em "obj_cb_query.from_id". Depois disso, pegamos o primeiro nome do remetente, que está no campo "from", e o convertamos para o formato string. O primeiro nome do remetente é armazenado em "obj_cb_query.from_first_name". A última coisa que fazemos com o primeiro nome é usar a função "decodeStringCharacters" para decodificar quaisquer caracteres especiais que possam existir.
Paralelamente, obtemos o sobrenome da pessoa que enviou a mensagem, transformamos em string e armazenamos em "obj_cb_query.from_last_name". Como antes, utilizamos "decodeStringCharacters" para revelar quaisquer caracteres especiais no sobrenome. O processo para obter o nome de usuário do remetente é o mesmo: extraímos o nome de usuário, armazenamos em "obj_cb_query.from_username" e utilizamos "decodeStringCharacters" para tratar qualquer caractere especial que possa atrapalhar o funcionamento adequado do nome no futuro.
Em seguida, focamos na mensagem associada à callback query. Pegamos o ID da mensagem no campo "message", convertemos para inteiro e armazenamos em "obj_cb_query.message_id". Enquanto isso, o texto da mensagem também é extraído e convertido para string, sendo armazenado em "obj_cb_query.message_text". Qualquer caractere especial no texto é decodificado. Depois, voltamos nossa atenção para os dados da callback. Extraímos esses dados, os convertemos para string e os armazenamos em "obj_cb_query.data". Como qualquer outro dado, os dados da callback são codificados com caracteres especiais.
Por fim, extraímos da callback query o ID do chat para o qual a mensagem foi enviada, o convertemos para inteiro e o colocamos em "obj_cb_query.chat_id". Isso nos dá o conjunto completo de informações sobre a callback query, incluindo quem era o usuário no chat, qual era a mensagem e quais eram os dados da callback. Em seguida, prosseguimos para processar os dados e atualizar a iteração.
ProcessCallbackQuery(obj_cb_query); //--- Call function to process the callback query. member_update_id = obj_item["update_id"].ToInt() + 1; //--- Update the last processed update ID for callback queries.
Aqui, chamamos a função "ProcessCallbackQuery", passando o objeto "obj_cb_query" como argumento. Essa função é responsável por lidar com a callback query e processar os detalhes extraídos anteriormente, como as informações do usuário, ID do chat, texto da mensagem e dados da callback. Ao chamar essa função, garantimos que a callback query seja tratada de forma apropriada com base em seu conteúdo específico.
Após processar a callback query, atualizamos o último ID de atualização processado ao recuperar o "update_id" do campo "obj_item", convertê-lo para inteiro e somar 1 a ele. Esse valor é armazenado em "member_update_id", que rastreia a atualização mais recente processada. Essa etapa garante que não processemos novamente a mesma callback query em iterações futuras, mantendo o controle do progresso de forma eficiente. Por fim, após processar a primeira mensagem, precisamos marcá-la como já tratada para evitar o reprocessamento.
member_first_remove = false; //--- After processing the first message, mark that the first message has been handled.
Atribuímos a variável "member_first_remove" com o valor "false" depois de lidarmos com a primeira mensagem. Isso significa que cuidamos da primeira mensagem, e se algo especial precisasse ser feito com ela, já foi feito. Por que realizamos esse passo? A finalidade é marcar que a primeira mensagem foi tratada e que não será tratada novamente. Ao fazer isso, garantimos que qualquer lógica futura que dependa da primeira mensagem estar não processada não será executada, pois não há necessidade.
O trecho completo do código-fonte responsável por obter e armazenar tanto mensagens de chat quanto callback queries está abaixo:
//+------------------------------------------------------------------+ //| Function to get chat updates from Telegram | //+------------------------------------------------------------------+ int Class_Bot_EA::getChatUpdates(void) { if (member_token == NULL) { //--- If bot token is empty Print("ERR: TOKEN EMPTY"); //--- Print error message indicating empty token. return (-1); //--- Return -1 to indicate error. } string out; //--- String to hold response data. string url = TELEGRAM_BASE_URL + "/bot" + member_token + "/getUpdates"; //--- Construct URL for Telegram API. string params = "offset=" + IntegerToString(member_update_id); //--- Set parameters including the offset based on the last update ID. int res = postRequest(out, url, params, WEB_TIMEOUT); //--- Send a POST request to Telegram with a timeout. if (res == 0) { //--- If request succeeds (res = 0) CJSONValue obj_json(NULL, jv_UNDEF); //--- Create a JSON object to parse the response. bool done = obj_json.Deserialize(out); //--- Deserialize the response. if (!done) { //--- If deserialization fails Print("ERR: JSON PARSING"); //--- Print error message indicating JSON parsing error. return (-1); //--- Return -1 to indicate error. } bool ok = obj_json["ok"].ToBool(); //--- Check if the response has "ok" field set to true. if (!ok) { //--- If "ok" field is false Print("ERR: JSON NOT OK"); //--- Print error message indicating that JSON response is not okay. return (-1); //--- Return -1 to indicate error. } int total = ArraySize(obj_json["result"].m_elements); //--- Get the total number of update elements. for (int i = 0; i < total; i++) { //--- Iterate through each update element. CJSONValue obj_item = obj_json["result"].m_elements[i]; //--- Access individual update element. if (obj_item["message"].m_type != jv_UNDEF) { //--- Check if the update has a message. Class_Message obj_msg; //--- Create an instance of Class_Message to store the message details. obj_msg.update_id = obj_item["update_id"].ToInt(); //--- Extract and store update ID. obj_msg.message_id = obj_item["message"]["message_id"].ToInt(); //--- Extract and store message ID. obj_msg.message_date = (datetime)obj_item["message"]["date"].ToInt(); //--- Extract and store message date. obj_msg.message_text = obj_item["message"]["text"].ToStr(); //--- Extract and store message text. obj_msg.message_text = decodeStringCharacters(obj_msg.message_text); //--- Decode any special characters in the message text. obj_msg.from_id = obj_item["message"]["from"]["id"].ToInt(); //--- Extract and store the sender's ID. obj_msg.from_first_name = obj_item["message"]["from"]["first_name"].ToStr(); //--- Extract and store the sender's first name. obj_msg.from_first_name = decodeStringCharacters(obj_msg.from_first_name); //--- Decode any special characters in the sender's first name. obj_msg.from_last_name = obj_item["message"]["from"]["last_name"].ToStr(); //--- Extract and store the sender's last name. obj_msg.from_last_name = decodeStringCharacters(obj_msg.from_last_name); //--- Decode any special characters in the sender's last name. obj_msg.from_username = obj_item["message"]["from"]["username"].ToStr(); //--- Extract and store the sender's username. obj_msg.from_username = decodeStringCharacters(obj_msg.from_username); //--- Decode any special characters in the sender's username. obj_msg.chat_id = obj_item["message"]["chat"]["id"].ToInt(); //--- Extract and store the chat ID. obj_msg.chat_first_name = obj_item["message"]["chat"]["first_name"].ToStr(); //--- Extract and store the chat's first name. obj_msg.chat_first_name = decodeStringCharacters(obj_msg.chat_first_name); //--- Decode any special characters in the chat's first name. obj_msg.chat_last_name = obj_item["message"]["chat"]["last_name"].ToStr(); //--- Extract and store the chat's last name. obj_msg.chat_last_name = decodeStringCharacters(obj_msg.chat_last_name); //--- Decode any special characters in the chat's last name. obj_msg.chat_username = obj_item["message"]["chat"]["username"].ToStr(); //--- Extract and store the chat's username. obj_msg.chat_username = decodeStringCharacters(obj_msg.chat_username); //--- Decode any special characters in the chat's username. obj_msg.chat_type = obj_item["message"]["chat"]["type"].ToStr(); //--- Extract and store the chat type. //--- Process the message based on chat ID. member_update_id = obj_msg.update_id + 1; //--- Update the last processed update ID. if (member_first_remove) { //--- If it's the first message after starting the bot continue; //--- Skip processing it. } //--- Check if we need to filter messages based on user or if no filter is applied. if (member_users_filter.Total() == 0 || (member_users_filter.Total() > 0 && member_users_filter.SearchLinear(obj_msg.from_username) >= 0)) { int index = -1; //--- Initialize index to -1 (indicating no chat found). for (int j = 0; j < member_chats.Total(); j++) { //--- Iterate through all chat objects. Class_Chat *chat = member_chats.GetNodeAtIndex(j); //--- Get chat object by index. if (chat.member_id == obj_msg.chat_id) { //--- If chat ID matches index = j; //--- Store the index. break; //--- Break the loop since we found the chat. } } if (index == -1) { //--- If no matching chat was found member_chats.Add(new Class_Chat); //--- Create a new chat object and add it to the list. Class_Chat *chat = member_chats.GetLastNode(); //--- Get the last (newly added) chat object. chat.member_id = obj_msg.chat_id; //--- Assign the chat ID. chat.member_time = TimeLocal(); //--- Record the current time for the chat. chat.member_state = 0; //--- Initialize the chat state to 0. chat.member_new_one.message_text = obj_msg.message_text; //--- Store the new message in the chat. chat.member_new_one.done = false; //--- Mark the new message as not processed. } else { //--- If matching chat was found Class_Chat *chat = member_chats.GetNodeAtIndex(index); //--- Get the chat object by index. chat.member_time = TimeLocal(); //--- Update the time for the chat. chat.member_new_one.message_text = obj_msg.message_text; //--- Store the new message. chat.member_new_one.done = false; //--- Mark the new message as not processed. } } } //--- Handle callback queries from Telegram. if (obj_item["callback_query"].m_type != jv_UNDEF) { //--- Check if there is a callback query in the update. Class_CallbackQuery obj_cb_query; //--- Create an instance of Class_CallbackQuery. obj_cb_query.id = obj_item["callback_query"]["id"].ToStr(); //--- Extract and store the callback query ID. obj_cb_query.from_id = obj_item["callback_query"]["from"]["id"].ToInt(); //--- Extract and store the sender's ID. obj_cb_query.from_first_name = obj_item["callback_query"]["from"]["first_name"].ToStr(); //--- Extract and store the sender's first name. obj_cb_query.from_first_name = decodeStringCharacters(obj_cb_query.from_first_name); //--- Decode any special characters in the sender's first name. obj_cb_query.from_last_name = obj_item["callback_query"]["from"]["last_name"].ToStr(); //--- Extract and store the sender's last name. obj_cb_query.from_last_name = decodeStringCharacters(obj_cb_query.from_last_name); //--- Decode any special characters in the sender's last name. obj_cb_query.from_username = obj_item["callback_query"]["from"]["username"].ToStr(); //--- Extract and store the sender's username. obj_cb_query.from_username = decodeStringCharacters(obj_cb_query.from_username); //--- Decode any special characters in the sender's username. obj_cb_query.message_id = obj_item["callback_query"]["message"]["message_id"].ToInt(); //--- Extract and store the message ID related to the callback. obj_cb_query.message_text = obj_item["callback_query"]["message"]["text"].ToStr(); //--- Extract and store the message text related to the callback. obj_cb_query.message_text = decodeStringCharacters(obj_cb_query.message_text); //--- Decode any special characters in the message text. obj_cb_query.data = obj_item["callback_query"]["data"].ToStr(); //--- Extract and store the callback data. obj_cb_query.data = decodeStringCharacters(obj_cb_query.data); //--- Decode any special characters in the callback data. obj_cb_query.chat_id = obj_item["callback_query"]["message"]["chat"]["id"].ToInt(); //--- Extract and store the chat ID. ProcessCallbackQuery(obj_cb_query); //--- Call function to process the callback query. member_update_id = obj_item["update_id"].ToInt() + 1; //--- Update the last processed update ID for callback queries. } } member_first_remove = false; //--- After processing the first message, mark that the first message has been handled. } return 0; //--- Return 0 to indicate successful processing of updates. }
Após obter as atualizações do chat, agora precisamos prosseguir com o processamento das respostas. Isso é tratado na seção a seguir.
Tratamento de Consultas de Callback para Ações dos Botões
Nesta seção, lidamos com mensagens recebidas e respondemos com botões inline com base em comandos específicos. A primeira coisa que precisamos fazer é processar o comando de inicialização ou a mensagem enviada pelo usuário e, a partir daí, podemos instanciar os botões inline e então buscar as callback queries. Não podemos pular essa etapa porque não podemos simplesmente fornecer um teclado inline sem antes receber um comando do usuário. Essa é a lógica que empregamos.
#define BTN_MENU "BTN_MENU" //--- Identifier for menu button //+------------------------------------------------------------------+ //| Process new messages | //+------------------------------------------------------------------+ void Class_Bot_EA::ProcessMessages(void){ //--- Loop through all chats for(int i=0; i<member_chats.Total(); i++){ Class_Chat *chat = member_chats.GetNodeAtIndex(i); //--- Get the current chat if(!chat.member_new_one.done){ //--- Check if the message has not been processed yet chat.member_new_one.done = true; //--- Mark the message as processed string text = chat.member_new_one.message_text; //--- Get the message text //--- Example of sending a message with inline buttons if (text == "Start" || text == "/start" || text == "Help" || text == "/help"){ string message = "Welcome! You can control me via inline buttons!"; //--- Welcome message //--- Define inline button to provide menu string buttons = "[[{\"text\": \"Provide Menu\", \"callback_data\": \""+BTN_MENU+"\"}]]"; sendMessageToTelegram(chat.member_id, message, customInlineKeyboardMarkup(buttons)); //--- Send the inline keyboard markup } } } }
Neste caso, configuramos uma função chamada "ProcessMessages" para lidar com as mensagens dos usuários que chegam ao nosso sistema. A primeira coisa que essa função faz é iterar sobre o conjunto de todos os chats que armazenamos em "member_chats". Para cada um desses chats, obtemos o objeto de chat correspondente chamando "GetNodeAtIndex(i)". Agora que temos o controle sobre o chat atual, verificamos se a mensagem em "member_new_one" já foi processada. Se ainda não tiver sido, marcamos como processada.
Em seguida, extraímos o conteúdo real da mensagem usando "chat.member_new_one.message_text". Avaliamos esse conteúdo para determinar se o usuário enviou algum comando, como "Start", "/start", "Help" ou "/help". Quando recebemos um comando como esses, retornamos uma mensagem que dá boas-vindas ao usuário e diz: "Você pode me controlar por meio dos botões inline!" Em seguida, definimos um botão callback inline que servirá como uma opção de menu para o usuário. Usamos o campo "callback_data" do botão para indicar que ele está relacionado ao "BTN_MENU". Formatamos o botão como um objeto JSON e o armazenamos na variável "buttons".
Em conclusão, a função "sendMessageToTelegram" é chamada para enviar a mensagem de boas-vindas e nosso teclado inline personalizado para o usuário. Essa função recebe três parâmetros: o "chat.member_id", a "message" e a marcação dos botões gerada pela função "customInlineKeyboardMarkup". A mensagem, juntamente com os botões inline, é enviada ao usuário. Agora ele pode interagir com os botões da forma como normalmente se interage com bots do Telegram. Como ainda somos iniciantes no assunto de teclado inline, vamos focar na lógica por trás disso.
string buttons = "[[{\"text\": \"Provide Menu\", \"callback_data\": \""+BTN_MENU+"\"}]]";
Desmembramento Detalhado:
Colchetes Externos: A string inteira está envolta em aspas duplas (" "), como é típico na definição de literais de string em muitas linguagens de programação. Dentro dessa string, vemos os caracteres "[[ ... ]]". Esses colchetes são usados para definir a estrutura do teclado inline:
- O primeiro conjunto de colchetes [ ... ] denota um array de linhas no teclado.
- O segundo conjunto de colchetes [ ... ] representa uma linha dentro desse array. Neste caso, há apenas uma linha.
Definição do Botão:
Dentro do segundo conjunto de colchetes, temos um objeto {"text": "Provide Menu", "callback_data": " + BTN_MENU + "}. Esse objeto define um único botão:
- "text": Essa chave especifica o rótulo do botão, que é "Provide Menu". Esse é o texto que aparecerá no botão quando o usuário o visualizar.
- "callback_data": Essa chave especifica os dados que serão enviados de volta ao bot quando o botão for clicado. Neste caso, o valor é "BTN_MENU", que é uma constante definida em outra parte do código. Isso permite que o bot reconheça qual botão foi clicado e responda de forma adequada.
Combinando Elementos:
A constante "BTN_MENU" é inserida na string JSON por meio de concatenação de strings. Isso permite a inclusão dinâmica dos dados do botão de callback. Por exemplo, se "BTN_MENU" for "BTN_MENU", o JSON resultante será: [{"text": "Provide Menu", "callback_data": "BTN_MENU"}].
Formato Final:
O formato final da string de botões, quando usada no código, é: "[ [{ "text": "Provide Menu", "callback_data": "BTN_MENU" }] ]". Esse formato especifica que há uma linha no teclado, e essa linha contém um botão.
Quando a API do Telegram recebe essa estrutura JSON, ela a interpreta como um teclado inline com um único botão. Quando um usuário clica nesse botão, o bot recebe os dados de callback "BTN_MENU" na callback query, que podem então ser usados para determinar a resposta apropriada. Na estrutura, usamos uma função personalizada para criar o botão inline. Sua lógica está descrita a seguir:
//+------------------------------------------------------------------+ //| Create a custom inline keyboard markup for Telegram | //+------------------------------------------------------------------+ string customInlineKeyboardMarkup(const string buttons){ //--- Construct the JSON string for the inline keyboard markup string result = "{\"inline_keyboard\": " + UrlEncode(buttons) + "}"; //--- Encode buttons as JSON return(result); }
A função "customInlineKeyboardMarkup" cria uma marcação de teclado inline personalizada para mensagens do Telegram. Para isso, começamos com um parâmetro de string, "buttons", que contém a estrutura JSON definindo os botões inline. Nosso trabalho é construir um objeto JSON que o Telegram possa usar para renderizar o teclado inline. Começamos formando a estrutura JSON com a chave "inline_keyboard". Em seguida, usamos a função "UrlEncode" para lidar com quaisquer caracteres especiais que possam estar presentes na string "buttons". Essa etapa de codificação é crucial porque, sem ela, podemos enfrentar problemas com caracteres especiais na definição dos botões. Após anexar a string de botões codificada, fechamos o objeto JSON. A string resultante é uma representação JSON válida da marcação do teclado inline. Retornamos essa string para que ela possa ser enviada à API do Telegram, que então renderizará o teclado interativamente na mensagem. Ao rodar o programa, temos a seguinte saída:
Podemos ver que foi um sucesso. De fato criamos o botão inline. No entanto, ainda não conseguimos responder aos cliques nele. Portanto, precisamos capturar a callback query recebida e responder adequadamente ao clique. Para isso, precisaremos criar uma função que obtenha os dados da query.
//+------------------------------------------------------------------+ //| Function to process callback queries | //+------------------------------------------------------------------+ void Class_Bot_EA::ProcessCallbackQuery(Class_CallbackQuery &cb_query) { Print("Callback Query ID: ", cb_query.id); //--- Log the callback query ID Print("Chat Token: ", member_token); //--- Log the member token Print("From First Name: ", cb_query.from_first_name); //--- Log the sender's first name Print("From Last Name: ", cb_query.from_last_name); //--- Log the sender's last name Print("From Username: ", cb_query.from_username); //--- Log the sender's username Print("Message ID: ", cb_query.message_id); //--- Log the message ID Print("Message Text: ", cb_query.message_text); //--- Log the message text Print("Callback Data: ", cb_query.data); //--- Log the callback data }
A função "ProcessCallbackQuery" gerencia os detalhes de uma callback query recebida do Telegram. Ela opera em uma instância da "Class_CallbackQuery", que contém todas as informações associadas à callback. Primeiro, ela registra o ID da callback query, que é um identificador único e essencial para rastrear e gerenciá-la. Em seguida, a função registra o "member_token". O papel desse token é indicar qual bot ou membro está processando a callback, garantindo assim que apenas o bot correto está lidando com a consulta.
Registramos então os nomes e sobrenomes do remetente usando "cb_query.from_first_name" e "cb_query.from_last_name", respectivamente. Essas informações permitem saber quem pressionou o botão inline e adicionar um toque mais pessoal, se for necessário se comunicar diretamente com o usuário no futuro. Falando nisso, também registramos o nome de usuário do remetente usando "cb_query.from_username". Isso nos dá outra forma de se referir diretamente ao usuário, se necessário. Depois de registrar a identidade do remetente, registramos o ID da mensagem associada à callback usando "cb_query.message_id". Saber esse ID nos permite saber a qual mensagem o clique no botão se refere.
Além disso, registramos o texto da mensagem usando "cb_query.message_text". Isso fornece contexto sobre a mensagem quando o botão foi clicado. Também registramos os dados da callback com "cb_query.data". Esses dados são o que foi enviado de volta pelo clique no botão e são usados para determinar qual ação tomar com base na interação do usuário. Ao registrar esses detalhes, obtemos uma visão completa da callback query. Isso é útil para depuração e para entender melhor como os usuários estão interagindo com o bot. Uma vez que rodamos o programa, essas são as saídas exibidas no terminal de negociação:
Agora que conseguimos obter as informações, podemos verificar qual botão foi clicado e gerar uma resposta adequada. No nosso caso, vamos utilizar os dados da callback da ação do botão de menu. Primeiro, definiremos as constantes dos botões. Adicionamos comentários detalhados para facilitar o entendimento.
#define BTN_NAME "BTN_NAME" //--- Identifier for name button #define BTN_INFO "BTN_INFO" //--- Identifier for info button #define BTN_QUOTES "BTN_QUOTES" //--- Identifier for quotes button #define BTN_MORE "BTN_MORE" //--- Identifier for more options button #define BTN_SCREENSHOT "BTN_SCREENSHOT" //--- Identifier for screenshot button #define EMOJI_CANCEL "\x274C" //--- Cross mark emoji #define EMOJI_UP "\x2B06" //--- Upwards arrow emoji #define BTN_BUY "BTN_BUY" //--- Identifier for buy button #define BTN_CLOSE "BTN_CLOSE" //--- Identifier for close button #define BTN_NEXT "BTN_NEXT" //--- Identifier for next button #define EMOJI_PISTOL "\xF52B" //--- Pistol emoji #define BTN_CONTACT "BTN_CONTACT" //--- Identifier for contact button #define BTN_JOIN "BTN_JOIN" //--- Identifier for join button
Após definir a função, podemos então prosseguir com as respostas.
//--- Respond based on the callback data string response_text; if (cb_query.data == BTN_MENU) { response_text = "You clicked "+BTN_MENU+"!"; //--- Prepare response text for BTN_MENU Print("RESPONSE = ", response_text); //--- Log the response //--- Send the response message to the correct group/channel chat ID sendMessageToTelegram(cb_query.chat_id, response_text, NULL); string message = "Information"; //--- Message to display options //--- Define inline buttons with callback data string buttons = "[[{\"text\": \"Get Expert's Name\", \"callback_data\": \""+BTN_NAME+"\"}]," "[{\"text\": \"Get Account Information\", \"callback_data\": \""+BTN_INFO+"\"}]," "[{\"text\": \"Get Current Market Quotes\", \"callback_data\": \""+BTN_QUOTES+"\"}]," "[{\"text\": \"More\", \"callback_data\": \""+BTN_MORE+"\"}, {\"text\": \"Screenshots\", \"callback_data\": \""+BTN_SCREENSHOT+"\"}, {\"text\": \""+EMOJI_CANCEL+"\", \"callback_data\": \""+EMOJI_CANCEL+"\"}]]"; sendMessageToTelegram(cb_query.chat_id, message, customInlineKeyboardMarkup(buttons)); //--- Send the inline keyboard markup }
Aqui, gerenciamos a resposta a uma callback query com base nos dados da callback. Começamos inicializando uma variável string, "response_text", para conter a mensagem que queremos enviar de volta ao usuário. Depois verificamos se o "callback_data" da callback query ("cb_query.data") corresponde à constante "BTN_MENU". Se corresponder, definimos "response_text" como "You clicked "+BTN_MENU+"!", reconhecendo o clique no botão e incluindo o identificador do botão pressionado. Registramos essa resposta usando a função "Print" para acompanhar o que está sendo enviado.
Em seguida, usamos a função "sendMessageToTelegram" para enviar a mensagem "response_text" ao chat identificado por "cb_query.chat_id". Como estamos enviando uma mensagem de texto simples sem teclado inline neste momento, o terceiro parâmetro é NULL, indicando que nenhuma marcação de teclado adicional será incluída.
Depois de enviar a mensagem inicial, preparamos uma nova mensagem com o texto "Information", que fornecerá ao usuário várias opções. Em seguida, definimos os botões inline usando uma estrutura similar a JSON na string "buttons". Essa estrutura inclui botões com rótulos como "Get Expert's Name", "Get Account Information", "Get Current Market Quotes", "More", "Screenshots" e "Cancel". Cada botão recebe valores específicos em "callback_data", como "BTN_NAME", "BTN_INFO", "BTN_QUOTES", "BTN_MORE", "BTN_SCREENSHOT" e "EMOJI_CANCEL", que ajudam a identificar qual botão foi pressionado.
Por fim, enviamos essa nova mensagem juntamente com o teclado inline usando a função "sendMessageToTelegram". O teclado inline é formatado em JSON pela função "customInlineKeyboardMarkup", garantindo que o Telegram possa exibir corretamente os botões Essa abordagem nos permite envolver os usuários de forma interativa, oferecendo várias opções diretamente na interface do Telegram. Ao compilar, obtemos os seguintes resultados:
Isso foi um sucesso. Agora precisamos responder aos respectivos dados de callback query recebidos dos botões inline fornecidos Começaremos com aquele responsável por obter o nome do programa.
else if (cb_query.data == BTN_NAME) { response_text = "You clicked "+BTN_NAME+"!"; //--- Prepare response text for BTN_NAME Print("RESPONSE = ", response_text); //--- Log the response string message = "The file name of the EA that I control is:\n"; //--- Message with EA file name message += "\xF50B"+__FILE__+" Enjoy.\n"; //--- Append the file name and a friendly message sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message }
Esta seção gerencia uma callback query específica em que o "callback_data" é igual a "BTN_NAME". Começamos configurando um texto de resposta na variável "response_text". Se o "callback_data" corresponder a "BTN_NAME", definimos "response_text" como "You clicked " + BTN_NAME + "!" Isso reconhece o clique no botão e inclui o identificador do botão clicado. Em seguida, mostramos essa resposta usando a função "Print" para monitorar o que está sendo enviado ao usuário.
Em seguida, criamos uma nova mensagem que transmite detalhes sobre o arquivo EA (Expert Advisor) sobre o qual o bot tem controle. Essa mensagem gerada pelo bot começa com as palavras: "O nome do arquivo do EA que eu controlo é:\n" e, em seguida, anexa o nome do arquivo-fonte atual, representado por FILE, à mensagem, encerrando com um amigável "Aproveite." Um toque curioso é que a mensagem se inicia com o caractere "\xF50B", que representa um ícone, um adorno tipográfico ou apenas uma forma de impressionar o leitor em nome do bot.
Para concluir, chamamos a função "sendMessageToTelegram" para enviar a mensagem ao chat correspondente a "cb_query.chat_id". Passamos NULL como terceiro parâmetro, o que significa que nenhum teclado inline acompanhará essa mensagem. Ao clicar no botão, temos a seguinte resposta.
Isso foi um sucesso. Agora, para obter a resposta dos outros botões — ou seja, informações da conta e cotações de preços de mercado — uma abordagem semelhante é utilizada.
else if (cb_query.data == BTN_INFO) { response_text = "You clicked "+BTN_INFO+"!"; //--- Prepare response text for BTN_INFO Print("RESPONSE = ", response_text); //--- Log the response ushort MONEYBAG = 0xF4B0; //--- Define money bag emoji string MONEYBAGcode = ShortToString(MONEYBAG); //--- Convert emoji code to string string currency = AccountInfoString(ACCOUNT_CURRENCY); //--- Get the account currency //--- Construct the account information message string message = "\x2733\Account No: "+(string)AccountInfoInteger(ACCOUNT_LOGIN)+"\n"; message += "\x23F0\Account Server: "+AccountInfoString(ACCOUNT_SERVER)+"\n"; message += MONEYBAGcode+"Balance: "+(string)AccountInfoDouble(ACCOUNT_BALANCE)+" "+currency+"\n"; message += "\x2705\Profit: "+(string)AccountInfoDouble(ACCOUNT_PROFIT)+" "+currency+"\n"; sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message } else if (cb_query.data == BTN_QUOTES) { response_text = "You clicked "+BTN_QUOTES+"!"; //--- Prepare response text for BTN_QUOTES Print("RESPONSE = ", response_text); //--- Log the response double Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get the current ask price double Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get the current bid price //--- Construct the market quotes message string message = "\xF170 Ask: "+(string)Ask+"\n"; message += "\xF171 Bid: "+(string)Bid+"\n"; sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message }
Ao compilar, obtemos os seguintes resultados:
Isso foi um sucesso. Agora, passamos a tratar também o botão inline "More". Até aqui, você pode ver que não poluímos a interface ou o campo de chat com muitas mensagens. Ela permanece limpa, e reutilizamos os botões inline de forma eficiente.
else if (cb_query.data == BTN_MORE) { response_text = "You clicked "+BTN_MORE+"!"; //--- Prepare response text for BTN_MORE Print("RESPONSE = ", response_text); //--- Log the response string message = "Choose More Options Below:\n"; //--- Message to prompt for additional options message += "Trading Operations"; //--- Title for trading operations //--- Define inline buttons for additional options string buttons = "[[{\"text\": \""+EMOJI_UP+"\", \"callback_data\": \""+EMOJI_UP+"\"}]," "[{\"text\": \"Buy\", \"callback_data\": \""+BTN_BUY+"\"}, {\"text\": \"Close\", \"callback_data\": \""+BTN_CLOSE+"\"}, {\"text\": \"Next\", \"callback_data\": \""+BTN_NEXT+"\"}]]"; sendMessageToTelegram(cb_query.chat_id, message, customInlineKeyboardMarkup(buttons)); //--- Send the inline keyboard markup }
Aqui, tratamos uma callback query cujo dado de retorno é "BTN_MORE". Começamos preparando uma mensagem de resposta armazenada na variável "response_text". Se os dados da callback corresponderem a "BTN_MORE", definimos "response_text" como "Você clicou em "+BTN_MORE+"!", reconhecendo o clique no botão e incluindo seu identificador. Essa resposta é registrada com a função "Print" para acompanhar o que está sendo enviado.
Em seguida, construímos uma nova mensagem que orienta o usuário a escolher entre mais opções. A variável "message" começa com "Escolha Mais Opções Abaixo:\n", seguida por "Operações de Negociação", que funciona como título para o conjunto de opções relacionadas a operações de trading. Depois, definimos os botões inline para essas opções adicionais usando uma estrutura similar a JSON na string "buttons". Essa estrutura inclui:
- Um botão com o emoji "EMOJI_UP" e seu respectivo "callback_data" como "EMOJI_UP".
- Uma linha de botões para várias operações de negociação: "Comprar", "Fechar" e "Próximo", cada um com seu respectivo "callback_data": "BTN_BUY", "BTN_CLOSE" e "BTN_NEXT".
Por fim, usamos a função "sendMessageToTelegram" para enviar essa mensagem juntamente com o teclado inline ao chat identificado por "cb_query.chat_id". A marcação do teclado inline é formatada em JSON pela função "customInlineKeyboardMarkup". Se clicarmos neste botão, devemos receber outro botão expandido. Isso é ilustrado abaixo:
Funcionou como o esperado. Agora só precisamos tratar os novos botões que surgem. Primeiro, o botão com o emoji para cima. Se ele for clicado, queremos voltar ao menu anterior, que é o menu principal.
else if (cb_query.data == EMOJI_UP) { response_text = "You clicked "+EMOJI_UP+"!"; //--- Prepare response text for EMOJI_UP Print("RESPONSE = ", response_text); //--- Log the response string message = "Choose a menu item:\n"; //--- Message to prompt for menu selection message += "Information"; //--- Title for information options //--- Define inline buttons for menu options string buttons = "[[{\"text\": \"Get Expert's Name\", \"callback_data\": \""+BTN_NAME+"\"}]," "[{\"text\": \"Get Account Information\", \"callback_data\": \""+BTN_INFO+"\"}]," "[{\"text\": \"Get Current Market Quotes\", \"callback_data\": \""+BTN_QUOTES+"\"}]," "[{\"text\": \"More\", \"callback_data\": \""+BTN_MORE+"\"}, {\"text\": \"Screenshots\", \"callback_data\": \""+BTN_SCREENSHOT+"\"}, {\"text\": \""+EMOJI_CANCEL+"\", \"callback_data\": \""+EMOJI_CANCEL+"\"}]]"; sendMessageToTelegram(cb_query.chat_id, message, customInlineKeyboardMarkup(buttons)); //--- Send the inline keyboard markup }
Aqui, apenas retornamos o teclado inline do menu principal. Com a mesma lógica, respondemos aos outros botões de operações de abertura e fechamento de posições como abaixo.
else if (cb_query.data == BTN_BUY) { response_text = "You clicked "+BTN_BUY+"!"; //--- Prepare response text for BTN_BUY Print("RESPONSE = ", response_text); //--- Log the response CTrade obj_trade; //--- Create a trade object double Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get the current ask price double Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get the current bid price //--- Open a buy position obj_trade.Buy(0.01, NULL, 0, Bid - 300 * _Point, Bid + 300 * _Point); double entry = 0, sl = 0, tp = 0, vol = 0; ulong ticket = obj_trade.ResultOrder(); //--- Get the ticket number of the new order if (ticket > 0) { if (PositionSelectByTicket(ticket)) { //--- Select the position by ticket entry = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get the entry price sl = PositionGetDouble(POSITION_SL); //--- Get the stop loss price tp = PositionGetDouble(POSITION_TP); //--- Get the take profit price vol = PositionGetDouble(POSITION_VOLUME); //--- Get the volume } } //--- Construct the message with position details string message = "\xF340\Opened BUY Position:\n"; message += "Ticket: "+(string)ticket+"\n"; message += "Open Price: "+(string)entry+"\n"; message += "Lots: "+(string)vol+"\n"; message += "SL: "+(string)sl+"\n"; message += "TP: "+(string)tp+"\n"; sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message } else if (cb_query.data == BTN_CLOSE) { response_text = "You clicked "+BTN_CLOSE+"!"; //--- Prepare response text for BTN_CLOSE Print("RESPONSE = ", response_text); //--- Log the response CTrade obj_trade; //--- Create a trade object int totalOpenBefore = PositionsTotal(); //--- Get the total number of open positions before closing obj_trade.PositionClose(_Symbol); //--- Close the position for the symbol int totalOpenAfter = PositionsTotal(); //--- Get the total number of open positions after closing //--- Construct the message with position closure details string message = "\xF62F\Closed Position:\n"; message += "Total Positions (Before): "+(string)totalOpenBefore+"\n"; message += "Total Positions (After): "+(string)totalOpenAfter+"\n"; sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message }
Ao executar o programa, estes são os resultados que obtemos:
Fantástico. Da mesma forma, adicionamos os outros segmentos de controle como abaixo.
else if (cb_query.data == BTN_NEXT) { response_text = "You clicked "+BTN_NEXT+"!"; //--- Prepare response text for BTN_NEXT Print("RESPONSE = ", response_text); //--- Log the response string message = "Choose Still More Options Below:\n"; //--- Message to prompt for further options message += "More Options"; //--- Title for more options //--- Define inline buttons for additional options string buttons = "[[{\"text\": \""+EMOJI_UP+"\", \"callback_data\": \""+EMOJI_UP+"\"}, {\"text\": \"Contact\", \"callback_data\": \""+BTN_CONTACT+"\"}, {\"text\": \"Join\", \"callback_data\": \""+BTN_JOIN+"\"},{\"text\": \""+EMOJI_PISTOL+"\", \"callback_data\": \""+EMOJI_PISTOL+"\"}]]"; sendMessageToTelegram(cb_query.chat_id, message, customInlineKeyboardMarkup(buttons)); //--- Send the inline keyboard markup } else if (cb_query.data == BTN_CONTACT) { response_text = "You clicked "+BTN_CONTACT+"!"; //--- Prepare response text for BTN_CONTACT Print("RESPONSE = ", response_text); //--- Log the response string message = "Contact the developer via link below:\n"; //--- Message with contact link message += "https://t.me/Forex_Algo_Trader"; sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message } else if (cb_query.data == BTN_JOIN) { response_text = "You clicked "+BTN_JOIN+"!"; //--- Prepare response text for BTN_JOIN Print("RESPONSE = ", response_text); //--- Log the response string message = "You want to be part of our MQL5 Community?\n"; //--- Message inviting to join the community message += "Welcome! <a href=\"https://t.me/forexalgo_trading\">Click me</a> to join.\n"; message += "<s>Civil Engineering</s> Forex AlgoTrading\n"; //--- Strikethrough text message += "<pre>This is a sample of our MQL5 code</pre>\n"; //--- Preformatted text message += "<u><i>Remember to follow community guidelines!\xF64F\</i></u>\n"; //--- Italic and underline text message += "<b>Happy Trading!</b>\n"; //--- Bold text sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message } else if (cb_query.data == EMOJI_PISTOL) { response_text = "You clicked "+EMOJI_PISTOL+"!"; //--- Prepare response text for EMOJI_PISTOL Print("RESPONSE = ", response_text); //--- Log the response string message = "Choose More Options Below:\n"; //--- Message to prompt for more options message += "Trading Operations"; //--- Title for trading operations //--- Define inline buttons for additional trading options string buttons = "[[{\"text\": \""+EMOJI_UP+"\", \"callback_data\": \""+EMOJI_UP+"\"}]," "[{\"text\": \"Buy\", \"callback_data\": \""+BTN_BUY+"\"}, {\"text\": \"Close\", \"callback_data\": \""+BTN_CLOSE+"\"}, {\"text\": \"Next\", \"callback_data\": \""+BTN_NEXT+"\"}]]"; sendMessageToTelegram(cb_query.chat_id, message, customInlineKeyboardMarkup(buttons)); //--- Send the inline keyboard markup }
Isso cuida dos botões gerados no campo "Next" e de sua responsividade. Em seguida, precisamos lidar com o botão de captura de tela que está no menu principal do teclado inline.
else if (cb_query.data == BTN_SCREENSHOT) { response_text = "You clicked "+BTN_SCREENSHOT+"!"; //--- Prepare response text for BTN_SCREENSHOT Print("RESPONSE = ", response_text); //--- Log the response string message = "Okay. Command 'get Current Chart Screenshot' received.\n"; //--- Message acknowledging screenshot command message += "Screenshot sending process initiated \xF60E"; //--- Emoji indicating process initiation sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message string caption = "Screenshot of Symbol: "+_Symbol+ //--- Caption for screenshot " ("+EnumToString(ENUM_TIMEFRAMES(_Period))+ //--- Timeframe ") @ Time: "+TimeToString(TimeCurrent()); //--- Current time //--- Send the screenshot to Telegram sendScreenshotToTelegram(cb_query.chat_id, _Symbol, _Period, caption); }
Por fim, precisamos cuidar do botão "Cancel", removendo os botões inline atuais, deixando tudo pronto para começar novamente.
else if (cb_query.data == EMOJI_CANCEL) { response_text = "You clicked "+EMOJI_CANCEL+"!"; //--- Prepare response text for EMOJI_CANCEL Print("RESPONSE = ", response_text); //--- Log the response string message = "Choose /start or /help to begin."; //--- Message for user guidance sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message //--- Reset the inline button state by removing the keyboard removeInlineButtons(member_token, cb_query.chat_id, cb_query.message_id); }
Aqui, uma abordagem semelhante à lógica usada anteriormente é implementada, com a adição de uma nova função para remover os botões inline.
//+------------------------------------------------------------------+ //| Remove inline buttons by editing message reply markup | //+------------------------------------------------------------------+ void removeInlineButtons(string memberToken, long chatID, long messageID){ //--- Reset the inline button state by removing the keyboard string url = TELEGRAM_BASE_URL + "/bot" + memberToken + "/editMessageReplyMarkup"; //--- API URL to edit message string params = "chat_id=" + IntegerToString(chatID) + //--- Chat ID parameter "&message_id=" + IntegerToString(messageID) + //--- Message ID parameter "&reply_markup=" + UrlEncode("{\"inline_keyboard\":[]}"); //--- Empty inline keyboard string response; int res = postRequest(response, url, params, WEB_TIMEOUT); //--- Send request to Telegram API }
Aqui, definimos a função "removeInlineButtons". Seu objetivo é remover botões inline de uma mensagem enviada anteriormente, alterando a marcação de resposta dessa mensagem. A função possui três parâmetros: "memberToken" (o token de autenticação do bot), "chatID" (ID do chat onde a mensagem foi enviada) e "messageID" (ID da mensagem que contém os botões inline). Primeiro, construímos a URL do endpoint da API do método "editMessageReplyMarkup" do Telegram. Fazemos isso combinando a "TELEGRAM_BASE_URL" com "/bot" e o "memberToken". Isso forma a URL que usaremos para nos comunicar com os servidores do Telegram.
Depois, especificamos a string "params", que contém os parâmetros exigidos pela chamada da API. Incluímos o parâmetro "chat_id". Para obter seu valor, convertemos a variável "chatID" de inteiro para string. Fazemos o mesmo para o parâmetro "message_id". Por fim, informamos à API que queremos remover os botões inline enviando um campo "reply_markup" vazio. O valor desse campo é uma string JSON vazia, obtida por meio do "UrlEncoding" da variável "emptyInlineKeyboard".
Depois de configurar os parâmetros, declaramos a variável "response" para armazenar o retorno do servidor e chamamos "postRequest" para enviar a requisição à API do Telegram. A função "postRequest" envia a solicitação usando a URL e os parâmetros fornecidos, junto com um tempo limite ("WEB_TIMEOUT") caso algo dê errado. Se a requisição for bem-sucedida, teremos o resultado desejado — uma mensagem sem botões inline, efetivamente resetando o estado dos botões. Se os dados da callback forem desconhecidos, exibimos uma mensagem informando que o botão clicado é desconhecido, ou seja, não foi reconhecido.
else { response_text = "Unknown button!"; //--- Prepare response text for unknown buttons Print("RESPONSE = ", response_text); //--- Log the response }
Ao clicarmos no botão de cancelamento, obtemos a seguinte saída:
Isso foi um sucesso. O código-fonte completo responsável por processar callback queries é o seguinte.
#define BTN_MENU "BTN_MENU" //--- Identifier for menu button //+------------------------------------------------------------------+ //| Process new messages | //+------------------------------------------------------------------+ void Class_Bot_EA::ProcessMessages(void){ //--- Loop through all chats for(int i=0; i<member_chats.Total(); i++){ Class_Chat *chat = member_chats.GetNodeAtIndex(i); //--- Get the current chat if(!chat.member_new_one.done){ //--- Check if the message has not been processed yet chat.member_new_one.done = true; //--- Mark the message as processed string text = chat.member_new_one.message_text; //--- Get the message text //--- Example of sending a message with inline buttons if (text == "Start" || text == "/start" || text == "Help" || text == "/help"){ string message = "Welcome! You can control me via inline buttons!"; //--- Welcome message //--- Define inline button to provide menu string buttons = "[[{\"text\": \"Provide Menu\", \"callback_data\": \""+BTN_MENU+"\"}]]"; sendMessageToTelegram(chat.member_id, message, customInlineKeyboardMarkup(buttons)); //--- Send the inline keyboard markup } } } } #define BTN_NAME "BTN_NAME" //--- Identifier for name button #define BTN_INFO "BTN_INFO" //--- Identifier for info button #define BTN_QUOTES "BTN_QUOTES" //--- Identifier for quotes button #define BTN_MORE "BTN_MORE" //--- Identifier for more options button #define BTN_SCREENSHOT "BTN_SCREENSHOT" //--- Identifier for screenshot button #define EMOJI_CANCEL "\x274C" //--- Cross mark emoji #define EMOJI_UP "\x2B06" //--- Upwards arrow emoji #define BTN_BUY "BTN_BUY" //--- Identifier for buy button #define BTN_CLOSE "BTN_CLOSE" //--- Identifier for close button #define BTN_NEXT "BTN_NEXT" //--- Identifier for next button #define EMOJI_PISTOL "\xF52B" //--- Pistol emoji #define BTN_CONTACT "BTN_CONTACT" //--- Identifier for contact button #define BTN_JOIN "BTN_JOIN" //--- Identifier for join button //+------------------------------------------------------------------+ //| Function to process callback queries | //+------------------------------------------------------------------+ void Class_Bot_EA::ProcessCallbackQuery(Class_CallbackQuery &cb_query) { Print("Callback Query ID: ", cb_query.id); //--- Log the callback query ID Print("Chat Token: ", member_token); //--- Log the member token Print("From First Name: ", cb_query.from_first_name); //--- Log the sender's first name Print("From Last Name: ", cb_query.from_last_name); //--- Log the sender's last name Print("From Username: ", cb_query.from_username); //--- Log the sender's username Print("Message ID: ", cb_query.message_id); //--- Log the message ID Print("Message Text: ", cb_query.message_text); //--- Log the message text Print("Callback Data: ", cb_query.data); //--- Log the callback data //--- Respond based on the callback data string response_text; if (cb_query.data == BTN_MENU) { response_text = "You clicked "+BTN_MENU+"!"; //--- Prepare response text for BTN_MENU Print("RESPONSE = ", response_text); //--- Log the response //--- Send the response message to the correct group/channel chat ID sendMessageToTelegram(cb_query.chat_id, response_text, NULL); string message = "Information"; //--- Message to display options //--- Define inline buttons with callback data string buttons = "[[{\"text\": \"Get Expert's Name\", \"callback_data\": \""+BTN_NAME+"\"}]," "[{\"text\": \"Get Account Information\", \"callback_data\": \""+BTN_INFO+"\"}]," "[{\"text\": \"Get Current Market Quotes\", \"callback_data\": \""+BTN_QUOTES+"\"}]," "[{\"text\": \"More\", \"callback_data\": \""+BTN_MORE+"\"}, {\"text\": \"Screenshots\", \"callback_data\": \""+BTN_SCREENSHOT+"\"}, {\"text\": \""+EMOJI_CANCEL+"\", \"callback_data\": \""+EMOJI_CANCEL+"\"}]]"; sendMessageToTelegram(cb_query.chat_id, message, customInlineKeyboardMarkup(buttons)); //--- Send the inline keyboard markup } else if (cb_query.data == BTN_NAME) { response_text = "You clicked "+BTN_NAME+"!"; //--- Prepare response text for BTN_NAME Print("RESPONSE = ", response_text); //--- Log the response string message = "The file name of the EA that I control is:\n"; //--- Message with EA file name message += "\xF50B"+__FILE__+" Enjoy.\n"; //--- Append the file name and a friendly message sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message } else if (cb_query.data == BTN_INFO) { response_text = "You clicked "+BTN_INFO+"!"; //--- Prepare response text for BTN_INFO Print("RESPONSE = ", response_text); //--- Log the response ushort MONEYBAG = 0xF4B0; //--- Define money bag emoji string MONEYBAGcode = ShortToString(MONEYBAG); //--- Convert emoji code to string string currency = AccountInfoString(ACCOUNT_CURRENCY); //--- Get the account currency //--- Construct the account information message string message = "\x2733\Account No: "+(string)AccountInfoInteger(ACCOUNT_LOGIN)+"\n"; message += "\x23F0\Account Server: "+AccountInfoString(ACCOUNT_SERVER)+"\n"; message += MONEYBAGcode+"Balance: "+(string)AccountInfoDouble(ACCOUNT_BALANCE)+" "+currency+"\n"; message += "\x2705\Profit: "+(string)AccountInfoDouble(ACCOUNT_PROFIT)+" "+currency+"\n"; sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message } else if (cb_query.data == BTN_QUOTES) { response_text = "You clicked "+BTN_QUOTES+"!"; //--- Prepare response text for BTN_QUOTES Print("RESPONSE = ", response_text); //--- Log the response double Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get the current ask price double Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get the current bid price //--- Construct the market quotes message string message = "\xF170 Ask: "+(string)Ask+"\n"; message += "\xF171 Bid: "+(string)Bid+"\n"; sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message } else if (cb_query.data == BTN_MORE) { response_text = "You clicked "+BTN_MORE+"!"; //--- Prepare response text for BTN_MORE Print("RESPONSE = ", response_text); //--- Log the response string message = "Choose More Options Below:\n"; //--- Message to prompt for additional options message += "Trading Operations"; //--- Title for trading operations //--- Define inline buttons for additional options string buttons = "[[{\"text\": \""+EMOJI_UP+"\", \"callback_data\": \""+EMOJI_UP+"\"}]," "[{\"text\": \"Buy\", \"callback_data\": \""+BTN_BUY+"\"}, {\"text\": \"Close\", \"callback_data\": \""+BTN_CLOSE+"\"}, {\"text\": \"Next\", \"callback_data\": \""+BTN_NEXT+"\"}]]"; sendMessageToTelegram(cb_query.chat_id, message, customInlineKeyboardMarkup(buttons)); //--- Send the inline keyboard markup } else if (cb_query.data == EMOJI_CANCEL) { response_text = "You clicked "+EMOJI_CANCEL+"!"; //--- Prepare response text for EMOJI_CANCEL Print("RESPONSE = ", response_text); //--- Log the response string message = "Choose /start or /help to begin."; //--- Message for user guidance sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message //--- Reset the inline button state by removing the keyboard removeInlineButtons(member_token, cb_query.chat_id, cb_query.message_id); } else if (cb_query.data == EMOJI_UP) { response_text = "You clicked "+EMOJI_UP+"!"; //--- Prepare response text for EMOJI_UP Print("RESPONSE = ", response_text); //--- Log the response string message = "Choose a menu item:\n"; //--- Message to prompt for menu selection message += "Information"; //--- Title for information options //--- Define inline buttons for menu options string buttons = "[[{\"text\": \"Get Expert's Name\", \"callback_data\": \""+BTN_NAME+"\"}]," "[{\"text\": \"Get Account Information\", \"callback_data\": \""+BTN_INFO+"\"}]," "[{\"text\": \"Get Current Market Quotes\", \"callback_data\": \""+BTN_QUOTES+"\"}]," "[{\"text\": \"More\", \"callback_data\": \""+BTN_MORE+"\"}, {\"text\": \"Screenshots\", \"callback_data\": \""+BTN_SCREENSHOT+"\"}, {\"text\": \""+EMOJI_CANCEL+"\", \"callback_data\": \""+EMOJI_CANCEL+"\"}]]"; sendMessageToTelegram(cb_query.chat_id, message, customInlineKeyboardMarkup(buttons)); //--- Send the inline keyboard markup } else if (cb_query.data == BTN_BUY) { response_text = "You clicked "+BTN_BUY+"!"; //--- Prepare response text for BTN_BUY Print("RESPONSE = ", response_text); //--- Log the response CTrade obj_trade; //--- Create a trade object double Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get the current ask price double Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get the current bid price //--- Open a buy position obj_trade.Buy(0.01, NULL, 0, Bid - 300 * _Point, Bid + 300 * _Point); double entry = 0, sl = 0, tp = 0, vol = 0; ulong ticket = obj_trade.ResultOrder(); //--- Get the ticket number of the new order if (ticket > 0) { if (PositionSelectByTicket(ticket)) { //--- Select the position by ticket entry = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get the entry price sl = PositionGetDouble(POSITION_SL); //--- Get the stop loss price tp = PositionGetDouble(POSITION_TP); //--- Get the take profit price vol = PositionGetDouble(POSITION_VOLUME); //--- Get the volume } } //--- Construct the message with position details string message = "\xF340\Opened BUY Position:\n"; message += "Ticket: "+(string)ticket+"\n"; message += "Open Price: "+(string)entry+"\n"; message += "Lots: "+(string)vol+"\n"; message += "SL: "+(string)sl+"\n"; message += "TP: "+(string)tp+"\n"; sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message } else if (cb_query.data == BTN_CLOSE) { response_text = "You clicked "+BTN_CLOSE+"!"; //--- Prepare response text for BTN_CLOSE Print("RESPONSE = ", response_text); //--- Log the response CTrade obj_trade; //--- Create a trade object int totalOpenBefore = PositionsTotal(); //--- Get the total number of open positions before closing obj_trade.PositionClose(_Symbol); //--- Close the position for the symbol int totalOpenAfter = PositionsTotal(); //--- Get the total number of open positions after closing //--- Construct the message with position closure details string message = "\xF62F\Closed Position:\n"; message += "Total Positions (Before): "+(string)totalOpenBefore+"\n"; message += "Total Positions (After): "+(string)totalOpenAfter+"\n"; sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message } else if (cb_query.data == BTN_NEXT) { response_text = "You clicked "+BTN_NEXT+"!"; //--- Prepare response text for BTN_NEXT Print("RESPONSE = ", response_text); //--- Log the response string message = "Choose Still More Options Below:\n"; //--- Message to prompt for further options message += "More Options"; //--- Title for more options //--- Define inline buttons for additional options string buttons = "[[{\"text\": \""+EMOJI_UP+"\", \"callback_data\": \""+EMOJI_UP+"\"}, {\"text\": \"Contact\", \"callback_data\": \""+BTN_CONTACT+"\"}, {\"text\": \"Join\", \"callback_data\": \""+BTN_JOIN+"\"},{\"text\": \""+EMOJI_PISTOL+"\", \"callback_data\": \""+EMOJI_PISTOL+"\"}]]"; sendMessageToTelegram(cb_query.chat_id, message, customInlineKeyboardMarkup(buttons)); //--- Send the inline keyboard markup } else if (cb_query.data == BTN_CONTACT) { response_text = "You clicked "+BTN_CONTACT+"!"; //--- Prepare response text for BTN_CONTACT Print("RESPONSE = ", response_text); //--- Log the response string message = "Contact the developer via link below:\n"; //--- Message with contact link message += "https://t.me/Forex_Algo_Trader"; sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message } else if (cb_query.data == BTN_JOIN) { response_text = "You clicked "+BTN_JOIN+"!"; //--- Prepare response text for BTN_JOIN Print("RESPONSE = ", response_text); //--- Log the response string message = "You want to be part of our MQL5 Community?\n"; //--- Message inviting to join the community message += "Welcome! <a href=\"https://t.me/forexalgo_trading\">Click me</a> to join.\n"; message += "<s>Civil Engineering</s> Forex AlgoTrading\n"; //--- Strikethrough text message += "<pre>This is a sample of our MQL5 code</pre>\n"; //--- Preformatted text message += "<u><i>Remember to follow community guidelines!\xF64F\</i></u>\n"; //--- Italic and underline text message += "<b>Happy Trading!</b>\n"; //--- Bold text sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message } else if (cb_query.data == EMOJI_PISTOL) { response_text = "You clicked "+EMOJI_PISTOL+"!"; //--- Prepare response text for EMOJI_PISTOL Print("RESPONSE = ", response_text); //--- Log the response string message = "Choose More Options Below:\n"; //--- Message to prompt for more options message += "Trading Operations"; //--- Title for trading operations //--- Define inline buttons for additional trading options string buttons = "[[{\"text\": \""+EMOJI_UP+"\", \"callback_data\": \""+EMOJI_UP+"\"}]," "[{\"text\": \"Buy\", \"callback_data\": \""+BTN_BUY+"\"}, {\"text\": \"Close\", \"callback_data\": \""+BTN_CLOSE+"\"}, {\"text\": \"Next\", \"callback_data\": \""+BTN_NEXT+"\"}]]"; sendMessageToTelegram(cb_query.chat_id, message, customInlineKeyboardMarkup(buttons)); //--- Send the inline keyboard markup } else if (cb_query.data == BTN_SCREENSHOT) { response_text = "You clicked "+BTN_SCREENSHOT+"!"; //--- Prepare response text for BTN_SCREENSHOT Print("RESPONSE = ", response_text); //--- Log the response string message = "Okay. Command 'get Current Chart Screenshot' received.\n"; //--- Message acknowledging screenshot command message += "Screenshot sending process initiated \xF60E"; //--- Emoji indicating process initiation sendMessageToTelegram(cb_query.chat_id, message, NULL); //--- Send the message string caption = "Screenshot of Symbol: "+_Symbol+ //--- Caption for screenshot " ("+EnumToString(ENUM_TIMEFRAMES(_Period))+ //--- Timeframe ") @ Time: "+TimeToString(TimeCurrent()); //--- Current time //--- Send the screenshot to Telegram sendScreenshotToTelegram(cb_query.chat_id, _Symbol, _Period, caption); } else { response_text = "Unknown button!"; //--- Prepare response text for unknown buttons Print("RESPONSE = ", response_text); //--- Log the response } //--- Optionally, reset the inline button state by removing the keyboard // removeInlineButtons(member_token, cb_query.chat_id, cb_query.message_id); } //+------------------------------------------------------------------+ //| Create a custom inline keyboard markup for Telegram | //+------------------------------------------------------------------+ string customInlineKeyboardMarkup(const string buttons){ //--- Construct the JSON string for the inline keyboard markup string result = "{\"inline_keyboard\": " + UrlEncode(buttons) + "}"; //--- Encode buttons as JSON return(result); } //+------------------------------------------------------------------+ //| Remove inline buttons by editing message reply markup | //+------------------------------------------------------------------+ void removeInlineButtons(string memberToken, long chatID, long messageID){ //--- Reset the inline button state by removing the keyboard string url = TELEGRAM_BASE_URL + "/bot" + memberToken + "/editMessageReplyMarkup"; //--- API URL to edit message string params = "chat_id=" + IntegerToString(chatID) + //--- Chat ID parameter "&message_id=" + IntegerToString(messageID) + //--- Message ID parameter "&reply_markup=" + UrlEncode("{\"inline_keyboard\":[]}"); //--- Empty inline keyboard string response; int res = postRequest(response, url, params, WEB_TIMEOUT); //--- Send request to Telegram API }
Resumidamente, gerenciamos callback queries respondendo a determinados cliques de botões e direcionando os usuários para opções apropriadas no teclado inline. Isso melhora a interação ao fornecer mensagens e opções contextualmente apropriadas. A seguir, testaremos a integração para garantir que essas funções funcionem como devem e que as interações sejam processadas corretamente.
Testando a Implementação dos Estados dos Botões Inline
Nesta seção, verificaremos como os botões inline interagem com o bot do Telegram e com o Expert Advisor em MQL5. Esse processo envolve simular ações de usuário, como pressionar botões, e garantir que o bot lide corretamente com as callback queries. Vamos avaliar a exibição, remoção ou atualização correta dos botões inline com base na interação do usuário. Para maior clareza, criamos um vídeo demonstrando como a integração funciona, mostrando passo a passo o comportamento do bot ao responder aos cliques nos botões inline. Isso garante que a configuração funcione como esperado em tempo real. Segue a ilustração:
Testamos as interações com os botões e as callback queries para garantir que o bot funcione corretamente com as entradas dos usuários e que os estados dos botões inline sejam atualizados ou redefinidos conforme necessário. Esse recurso oferece um estilo de interação não linear, o que aumenta o engajamento e proporciona uma experiência mais eficiente ao controlar o bot por meio do Telegram.
Conclusão
Para concluir, implementamos e testamos o processamento de callback queries e botões inline no bot do Telegram. Agora, o bot pode responder às entradas dos usuários com mensagens personalizadas e oferecer opções interativas por meio de teclados inline. A experiência do usuário foi aprimorada com a adição de botões em tempo real e de fácil utilização para ações como acessar menus, obter informações sobre o expert ou executar comandos relacionados à negociação.
Testamos o sistema e podemos confirmar que ele funciona conforme o esperado, processando corretamente cada callback query e fornecendo ao usuário os retornos apropriados. Ao realizar essas tarefas, o bot ainda mantém uma qualidade conversacional, o que contribui para manter o interesse e a usabilidade por parte do usuário Podemos perceber que os botões inline são mais eficientes, pois não poluem o campo de chat, exatamente como planejado. Esperamos que você tenha achado o artigo detalhado e de fácil compreensão.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15823
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Confira o novo artigo: Criando um Expert Advisor Integrado MQL5-Telegram (Parte 6): Adding Responsive Inline Buttons (Adicionando botões responsivos em linha).
Autor: Allan Munene Mutiiria
Isso é fantástico! Você sempre fornece insights valiosos, e eu realmente aprecio isso. Obrigado, estimado Sir Allan.
Isso é fantástico! Você sempre fornece insights valiosos, e eu realmente aprecio isso. Obrigado, estimado Sir Allan.
Excelente trabalho! Obrigado!
@Javier Santiago Gaston De Iriarte Cabrera, obrigado pelo feedback gentil e pelo reconhecimento. Você é muito bem-vindo.