2026-09-17 10:25:08 -05:00 | | | //+------------------------------------------------------------------+
|
| | | //| Telegram.mqh |
|
| | | //| Copyright 2014, MetaQuotes Software Corp. |
|
| | | //| http://www.mql5.com |
|
| | | //+------------------------------------------------------------------+
|
| | | #property copyright "Copyright 2014, MetaQuotes Software Corp."
|
| | | #property link "http://www.mql5.com"
|
| | | #property strict
|
| | |
|
| | | #ifndef TELEGRAMBYLEO_TELEGRAM_MQH
|
| | | #define TELEGRAMBYLEO_TELEGRAM_MQH
|
| | |
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Include |
|
| | | //+------------------------------------------------------------------+
|
| | | #include <Arrays\List.mqh>
|
| | | #include <Arrays\ArrayLong.mqh>
|
| | | #include "Common.mqh"
|
| | | #include "..\\JsonParserByLeo\\Src\\JsonNode.mqh"
|
| | | #include <TSN\\MQLArticles\\Utils\\FA\\Logger.mqh>
|
| | |
|
2026-09-22 17:06:21 -05:00 | | |
|
2026-09-17 10:25:08 -05:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| CCustomChat |
|
| | | //+------------------------------------------------------------------+
|
| | | class CCustomChat : public CObject
|
| | | {
|
| | | public:
|
| | | long m_id;
|
| | | CCustomMessage m_last;
|
| | | CCustomMessage m_new_one;
|
| | | int m_state;
|
| | | datetime m_time;
|
| | | };
|
| | |
|
| | |
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| ChatActionToString |
|
| | | //+------------------------------------------------------------------+
|
| | | string ChatActionToString(const ENUM_CHAT_ACTION _action)
|
| | | {
|
| | | string result = EnumToString(_action);
|
| | | result = StringSubstr(result, 7);
|
| | | StringToLower(result);
|
| | | return(result);
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | struct CCustomMessage
|
| | | {
|
| | | //---
|
| | | string chat_first_name;
|
| | | string chat_last_name;
|
| | | string chat_username;
|
| | | string from_first_name;
|
| | | string from_last_name;
|
| | | string from_username;
|
| | | string message_text;
|
| | | string chat_type;
|
| | |
|
| | | //---
|
| | | long update_id;
|
| | | long message_id;
|
| | | long from_id;
|
| | | long chat_id;
|
| | | datetime message_date;
|
| | |
|
| | | //---
|
| | | bool done;
|
| | |
|
| | |
|
| | | //---
|
| | | CCustomMessage()
|
| | | {
|
| | | done = false;
|
| | | update_id = 0;
|
| | | message_id = 0;
|
| | | from_id = 0;
|
| | | from_first_name = NULL;
|
| | | from_last_name = NULL;
|
| | | from_username = NULL;
|
| | | chat_id = 0;
|
| | | chat_first_name = NULL;
|
| | | chat_last_name = NULL;
|
| | | chat_username = NULL;
|
| | | chat_type = NULL;
|
| | | message_date = 0;
|
| | | message_text = NULL;
|
| | | from_id = 0;
|
| | | from_first_name = NULL;
|
| | | from_last_name = NULL;
|
| | | from_username = NULL;
|
| | | chat_id = 0;
|
| | | chat_first_name = NULL;
|
| | | chat_last_name = NULL;
|
| | | chat_username = NULL;
|
| | | chat_type = NULL;
|
| | | message_date = 0;
|
| | | message_text = NULL;
|
| | | }
|
| | | };
|
| | |
|
| | |
|
| | |
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Clase para poder enviar mensajes |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTlgBotMessageSender : public CCustomBot
|
| | | {
|
| | | protected:
|
| | | long m_chat_id; // ChatId
|
| | | TSN::CJsonParser m_json;
|
| | |
|
| | | public:
|
| | |
|
| | | //--- Basic setters \ getters
|
| | | // ChatId
|
| | | void ChatId(const long _chat_id) { this.m_chat_id = _chat_id; }
|
| | | inline long ChatId() const { return m_chat_id; }
|
| | |
|
| | |
|
| | | //--- Enviar a telegram
|
| | | // Enviar mensajes
|
| | | int SendMessage(const string _text, const string _reply_markup = NULL, const ENUM_TELEGRAM_PARSE_MODE _parse_mode = WRONG_VALUE, const bool _silently = false);
|
| | |
|
| | | // Enviar fotos
|
| | | int SendPhoto(string &_photo_id, const string _local_path, const string _caption = NULL, const bool _common_flag = false, const int _timeout = 10000);
|
| | |
|
| | | // Enviar una foto por id
|
| | | int SendPhoto(const string _photo_id, const string _caption = NULL);
|
| | |
|
| | | // Chat action
|
| | | int SendChatAction(const ENUM_CHAT_ACTION _action);
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | int CTlgBotMessageSender::SendMessage(
|
| | | const string _text,
|
| | | const string _reply_markup = NULL,
|
| | | const ENUM_TELEGRAM_PARSE_MODE _parse_mode = WRONG_VALUE,
|
| | | const bool _silently = false)
|
| | | {
|
| | | //--- check token
|
| | | if(m_token == NULL)
|
| | | return(ERR_TOKEN_ISEMPTY);
|
| | |
|
| | | //---
|
| | | //string out;
|
| | | //---
|
| | | string params = StringFormat("chat_id=%lld&text=%s", m_chat_id, UrlEncode(_text));
|
| | | if(_reply_markup != NULL)
|
| | | params += "&reply_markup=" + _reply_markup;
|
| | | if(_parse_mode != WRONG_VALUE)
|
| | | params += "&parse_mode=" + g_telegram_parse_mode_str[_parse_mode];
|
| | | if(_silently)
|
| | | params += "&disable_notification=true";
|
| | |
|
| | | //---
|
| | | return (PostRequest(StringFormat("%s/bot%s/sendMessage", TELEGRAM_BASE_URL, m_token),
|
| | | params,
|
| | | WEB_TIMEOUT));
|
| | | }
|
| | |
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | int CTlgBotMessageSender::SendPhoto(
|
| | | const string _photo_id,
|
| | | const string _caption = NULL)
|
| | | {
|
| | | if(m_token == NULL)
|
| | | return(ERR_TOKEN_ISEMPTY);
|
| | |
|
| | | //---
|
| | | string url = StringFormat("%s/bot%s/sendPhoto", TELEGRAM_BASE_URL, m_token);
|
| | | string params = StringFormat("chat_id=%lld&photo=%s", m_chat_id, _photo_id);
|
| | | if(_caption != NULL)
|
| | | params += "&caption=" + UrlEncode(_caption);
|
| | |
|
| | | //---
|
| | | const int res = PostRequest(m_json.m_raw, m_json.m_offset, url, params, WEB_TIMEOUT);
|
| | | if(res != 0)
|
| | | {
|
| | | //--- parse result
|
| | | m_json.CalcLen(); // Del json dado calculamos el len
|
| | | m_json.CorrectPadding();
|
| | | bool done = m_json.Parse();
|
| | | if(!done)
|
| | | return(ERR_JSON_PARSING);
|
| | |
|
| | | //---
|
| | | TSN::CJsonNode root = m_json.GetRoot();
|
| | |
|
| | | //--- get error description
|
| | | bool ok = root["ok"].ToBool(false); // por defecto false
|
| | | long err_code = root["error_code"].ToInt(0);
|
| | | string err_desc = root["description"].ToString();
|
| | |
|
| | | //---
|
| | | LogError(StringFormat("Ok = %d, err code = %d, err desc = %s", ok, err_code, err_desc), FUNCION_ACTUAL);
|
| | | }
|
| | | //--- done
|
| | | return(res);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | int CTlgBotMessageSender::SendPhoto(string &_photo_id,
|
| | | const string _local_path,
|
| | | const string _caption = NULL,
|
| | | const bool _common_flag = false,
|
| | | const int _timeout = 10000)
|
| | | {
|
| | | if(m_token == NULL)
|
| | | return(ERR_TOKEN_ISEMPTY);
|
| | |
|
| | | ResetLastError();
|
| | | //--- copy file to memory buffer
|
| | | if(!FileIsExist(_local_path, _common_flag))
|
| | | return(ERR_FILE_NOT_EXIST);
|
| | |
|
| | | //---
|
| | | int flags = FILE_READ | FILE_BIN | FILE_SHARE_WRITE | FILE_SHARE_READ;
|
| | | if(_common_flag)
|
| | | flags |= FILE_COMMON;
|
| | |
|
| | | //---
|
| | | int file = FileOpen(_local_path, flags);
|
| | | if(file < 0)
|
| | | return(_LastError);
|
| | |
|
| | | //---
|
| | | int file_size = (int)FileSize(file);
|
| | | uchar photo[];
|
| | | ArrayResize(photo, file_size);
|
| | | FileReadArray(file, photo, 0, file_size);
|
| | | FileClose(file);
|
| | |
|
| | | //--- create boundary: (data -> base64 -> 1024 bytes -> md5)
|
| | | uchar base64[];
|
| | | uchar key[];
|
| | | CryptEncode(CRYPT_BASE64, photo, key, base64);
|
| | | //---
|
| | | uchar temp[1024] = {0};
|
| | | ArrayCopy(temp, base64, 0, 0, 1024);
|
| | | //---
|
| | | uchar md5[];
|
| | | CryptEncode(CRYPT_HASH_MD5, temp, key, md5);
|
| | | //---
|
| | | string hash = NULL;
|
| | | int total = ArraySize(md5);
|
| | | for(int i = 0; i < total; i++)
|
| | | hash += StringFormat("%02X", md5[i]);
|
| | | hash = StringSubstr(hash, 0, 16);
|
| | |
|
| | | //--- WebRequest
|
| | | string result_headers;
|
| | |
|
| | | string url = StringFormat("%s/bot%s/sendPhoto", TELEGRAM_BASE_URL, m_token);
|
| | |
|
| | | //--- 1
|
| | | char data[];
|
| | |
|
| | | //--- add chart_id
|
| | | ArrayAdd(data, "\r\n");
|
| | | ArrayAdd(data, "--" + hash + "\r\n");
|
| | | ArrayAdd(data, "Content-Disposition: form-data; name=\"chat_id\"\r\n");
|
| | | ArrayAdd(data, "\r\n");
|
| | | ArrayAdd(data, IntegerToString(m_chat_id));
|
| | | ArrayAdd(data, "\r\n");
|
| | |
|
| | | if(StringLen(_caption) > 0)
|
| | | {
|
| | | ArrayAdd(data, "--" + hash + "\r\n");
|
| | | ArrayAdd(data, "Content-Disposition: form-data; name=\"caption\"\r\n");
|
| | | ArrayAdd(data, "\r\n");
|
| | | ArrayAdd(data, _caption);
|
| | | ArrayAdd(data, "\r\n");
|
| | | }
|
| | |
|
| | | ArrayAdd(data, "--" + hash + "\r\n");
|
| | | ArrayAdd(data, "Content-Disposition: form-data; name=\"photo\"; filename=\"lampash.gif\"\r\n");
|
| | | ArrayAdd(data, "\r\n");
|
| | |
|
| | | // Convert uchar photo[] to char[] before adding
|
| | | char photo_char[];
|
| | | ArrayResize(photo_char, file_size);
|
| | | for(int i = 0; i < file_size; i++)
|
| | | {
|
| | | photo_char[i] = (char)photo[i];
|
| | | }
|
| | | ArrayAdd(data, photo_char);
|
| | |
|
| | | ArrayAdd(data, "\r\n");
|
| | | ArrayAdd(data, "--" + hash + "--\r\n");
|
| | |
|
| | | // SaveToFile("debug.txt",data);
|
| | |
|
| | | //---
|
| | | const string headers = "Content-Type: multipart/form-data; boundary=" + hash + "\r\n";
|
| | | const int res = WebRequest("POST", url, headers, _timeout, data, m_json.m_raw, result_headers);
|
| | | if(res == 200) //OK
|
| | | {
|
| | | //--- delete BOM
|
| | | m_json.m_offset = 0;
|
| | | int size = ArraySize(m_json.m_raw);
|
| | | for(int i = 0; i < fmin(size, 8); i++)
|
| | | {
|
| | | // Cast to uchar for proper BOM comparison
|
| | | const uchar byte_val = (uchar)m_json.m_raw[i];
|
| | | if(byte_val == 0xef || byte_val == 0xbb || byte_val == 0xbf)
|
| | | m_json.m_offset = i + 1;
|
| | | else
|
| | | break;
|
| | | }
|
| | |
|
| | | //--- parse result
|
| | | m_json.CalcLen();
|
| | | m_json.CorrectPadding();
|
| | | const bool done = m_json.Parse();
|
| | | if(!done)
|
| | | return(ERR_JSON_PARSING);
|
| | | TSN::CJsonNode root = m_json.GetRoot();
|
| | |
|
| | | //--- get error description
|
| | | const bool ok = root["ok"].ToBool(false);
|
| | | if(!ok)
|
| | | return(ERR_JSON_NOT_OK);
|
| | |
|
| | | //---
|
| | | CJsonIteratorArray it = root["result"]["photo"].BeginArr();
|
| | |
|
| | | //---
|
| | | while(it.IsValid())
|
| | | {
|
| | | TSN::CJsonNode image = it.Val();
|
| | | const long image_size = image["file_size"].ToInt(0);
|
| | | if(image_size <= file_size)
|
| | | _photo_id = image["file_id"].ToString();
|
| | |
|
| | | it.Next();
|
| | | }
|
| | |
|
| | | //---
|
| | | return(0);
|
| | | }
|
| | | else
|
| | | {
|
| | | if(res == -1)
|
| | | {
|
| | | return(_LastError);
|
| | | }
|
| | | else
|
| | | {
|
| | | if(res >= 100 && res <= 511)
|
| | | {
|
| | | LogError(CharArrayToString(m_json.m_raw, 0, WHOLE_ARRAY, CP_UTF8), FUNCION_ACTUAL);
|
| | | return(ERR_HTTP_ERROR_FIRST + res);
|
| | | }
|
| | | return(res);
|
| | | }
|
| | | }
|
| | | //---
|
| | | return(0);
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | int CTlgBotMessageSender::SendChatAction(const ENUM_CHAT_ACTION _action)
|
| | | {
|
| | | if(m_token == NULL)
|
| | | return(ERR_TOKEN_ISEMPTY);
|
| | | // string out;
|
| | | string url = StringFormat("%s/bot%s/sendChatAction", TELEGRAM_BASE_URL, m_token);
|
| | | string params = StringFormat("chat_id=%lld&action=%s", m_chat_id, ChatActionToString(_action));
|
| | | int res = PostRequest(url, params, WEB_TIMEOUT);
|
| | | return(res);
|
| | | }
|
| | |
|
| | | /*
|
| | | "<u>underlined</u>\n"
|
| | | "<b>bold</b>\n"
|
| | | "<i>italic</i>\n"
|
| | | "<s>strikethrough</s>\n"
|
| | | "<a href=\"http://www.example.com/\">URL</a>\n"
|
| | | "<code>inline fixed-width code</code>\n"
|
| | | "<pre>pre-formatted monospace</pre></font>\n"
|
| | | "<pre><code class=\"language-MQL5\">pre-formatted fixed-width code block written in the MQL5 programming language</code></pre>\n"
|
| | | "<blockquote>Block quotation started\nBlock quotation continued\nThe last line of the block quotation</blockquote>"
|
| | | "<tg-spoiler>spoiler</tg-spoiler>\n"
|
| | | */
|
| | |
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Clase de bot de telegram full (obtiene mensajes) |
|
| | | //+------------------------------------------------------------------+
|
| | | class CTlgFullBot : public CTlgBotMessageSender
|
| | | {
|
| | | protected:
|
| | | CList m_chats;
|
| | | string m_name;
|
| | | long m_update_id;
|
| | | CArrayLong m_chats_id_permitidos;
|
| | | bool m_first_remove;
|
| | |
|
| | | public:
|
| | | CTlgFullBot(void);
|
| | | ~CTlgFullBot(void) {}
|
| | |
|
| | |
|
| | | //---
|
| | | __forceinline int ChatsTotal() const { return(m_chats.Total()); }
|
| | | __forceinline string Name() const { return(m_name); }
|
| | | __forceinline CArrayLong* PermitedChats() { return &m_chats_id_permitidos; }
|
| | |
|
| | | //---
|
| | | int GetMe();
|
| | | int GetUpdates();
|
| | |
|
| | | //---
|
| | | virtual void ProcessMessages(void) {};
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | CTlgFullBot::CTlgFullBot(void)
|
| | | {
|
| | | m_name = NULL;
|
| | | m_update_id = 0;
|
| | | m_first_remove = true;
|
| | | m_chats.Clear();
|
| | | m_chats_id_permitidos.Clear();
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | int CTlgFullBot::GetMe()
|
| | | {
|
| | | if(m_token == NULL)
|
| | | return(ERR_TOKEN_ISEMPTY);
|
| | | //---
|
| | | string url = StringFormat("%s/bot%s/getMe", TELEGRAM_BASE_URL, m_token);
|
| | | string params = "";
|
| | | int res = PostRequest(m_json.m_raw, m_json.m_offset, url, params, WEB_TIMEOUT);
|
| | | if(res == 0)
|
| | | {
|
| | | m_json.CalcLen();
|
| | | m_json.CorrectPadding();
|
| | | if(!m_json.Parse())
|
| | | return(ERR_JSON_PARSING);
|
| | |
|
| | | //---
|
| | | TSN::CJsonNode root = m_json.GetRoot();
|
| | | const bool ok = root["ok"].ToBool(false);
|
| | | if(!ok)
|
| | | {
|
| | | long err_code = root["error_code"].ToInt(0);
|
| | | string err_desc = root["description"].ToString();
|
| | | LogError(StringFormat("Ok = %d, err code = %d, err desc = %s", ok, err_code, err_desc), FUNCION_ACTUAL);
|
| | | return(ERR_JSON_NOT_OK);
|
| | | }
|
| | |
|
| | | //---
|
| | | if(m_name == NULL)
|
| | | m_name = root["result"]["username"].ToString();
|
| | | }
|
| | | //---
|
| | | return(res);
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | int CTlgFullBot::GetUpdates()
|
| | | {
|
| | | if(m_token == NULL)
|
| | | return(ERR_TOKEN_ISEMPTY);
|
| | |
|
| | | //---
|
| | | string url = StringFormat("%s/bot%s/getUpdates", TELEGRAM_BASE_URL, m_token);
|
| | | string params = StringFormat("offset=%d", m_update_id);
|
| | |
|
| | | //---
|
| | | const int res = PostRequest(m_json.m_raw, m_json.m_offset, url, params, WEB_TIMEOUT);
|
| | | if(res == 0)
|
| | | {
|
| | | //---
|
| | | m_json.CalcLen();
|
| | | m_json.CorrectPadding();
|
| | | if(!m_json.Parse())
|
| | | return(ERR_JSON_PARSING);
|
| | |
|
| | | //---
|
| | | TSN::CJsonNode root = m_json.GetRoot();
|
| | | const bool ok = root["ok"].ToBool(false);
|
2026-09-23 15:26:06 -05:00 | | | if(!ok)
|
2026-09-17 10:25:08 -05:00 | | | {
|
| | | long err_code = root["error_code"].ToInt(0);
|
| | | string err_desc = root["description"].ToString();
|
| | | LogError(StringFormat("Ok = %d, err code = %d, err desc = %s", ok, err_code, err_desc), FUNCION_ACTUAL);
|
| | | return(ERR_JSON_NOT_OK);
|
| | | }
|
| | |
|
| | | //---
|
| | | CCustomMessage msg;
|
| | |
|
| | | //---
|
| | | //Print("num: " , root["result"].Size());
|
| | | CJsonIteratorArray it = root["result"].BeginArr();
|
| | | while(it.IsValid())
|
| | | {
|
| | | TSN::CJsonNode item = it.Val();
|
| | |
|
| | | //---
|
| | | m_update_id = item["update_id"].ToInt(0) + 1;
|
| | | if(m_first_remove)
|
| | | {
|
| | | it.Next();
|
| | | continue;
|
| | | }
|
| | |
|
| | | //--- Chat
|
| | | const long chat_id = item["message"]["chat"]["id"].ToInt(0);
|
| | | if(m_chats_id_permitidos.SearchLinear(chat_id) < 0)
|
| | | {
|
| | | // Print("INvalid chat");
|
| | | // Print(item.ComplexToString());
|
| | | it.Next();
|
| | | continue;
|
| | | }
|
| | | // Print("it");
|
| | |
|
| | | //---
|
| | | msg.chat_id = chat_id;
|
| | | msg.chat_first_name = item["message"]["chat"]["first_name"].ToString();
|
| | | msg.chat_last_name = item["message"]["chat"]["last_name"].ToString();
|
| | | msg.chat_username = item["message"]["chat"]["username"].ToString();
|
| | | msg.chat_type = item["message"]["chat"]["type"].ToString();
|
| | |
|
| | | //--- Update id
|
| | | msg.update_id = m_update_id - 1;
|
| | |
|
| | | //--- Message
|
| | | msg.message_id = item["message"]["message_id"].ToInt(0);
|
| | | msg.message_date = (datetime)item["message"]["date"].ToInt(0);
|
| | | msg.message_text = item["message"]["text"].ToString();
|
| | |
|
| | | //--- From
|
| | | msg.from_id = item["message"]["from"]["id"].ToInt(0);
|
| | | msg.from_first_name = item["message"]["from"]["first_name"].ToString();
|
| | | msg.from_last_name = item["message"]["from"]["last_name"].ToString();
|
| | | msg.from_username = item["message"]["from"]["username"].ToString();
|
| | |
|
| | | //--- find the chat
|
| | | int index = -1;
|
| | | for(int j = 0; j < m_chats.Total(); j++)
|
| | | {
|
| | | CCustomChat *chat = m_chats.GetNodeAtIndex(j);
|
| | | if(chat.m_id == msg.chat_id)
|
| | | {
|
| | | index = j;
|
| | | break;
|
| | | }
|
| | | }
|
| | |
|
| | | //Print("sasas");
|
| | | //--- add new one to the chat list
|
| | | if(index == -1)
|
| | | {
|
| | | m_chats.Add(new CCustomChat());
|
| | | CCustomChat *chat = m_chats.GetLastNode();
|
| | | chat.m_id = msg.chat_id;
|
| | | chat.m_time = TimeLocal();
|
| | | chat.m_state = 0;
|
| | | chat.m_new_one = msg;
|
| | | chat.m_new_one.done = false;
|
| | | }
|
| | | else
|
| | | {
|
| | | CCustomChat *chat = m_chats.GetNodeAtIndex(index);
|
| | | chat.m_time = TimeLocal();
|
| | | chat.m_new_one = msg;
|
| | | chat.m_new_one.done = false;
|
| | | }
|
| | |
|
| | | it.Next();
|
| | | }
|
| | |
|
| | | m_first_remove = false;
|
| | | }
|
| | | //---
|
| | | return(res);
|
| | | }
|
| | |
|
| | |
|
| | | #endif
|
| | | //+--------
|
| | | //+------------------------------------------------------------------+
|