
MQL5-Telegram統合エキスパートアドバイザーの作成(第6回):レスポンシブなインラインボタンの追加
はじめに
この記事では、Telegramを通じてMetaQuotes Language 5 (MQL5)エキスパートアドバイザー(EA)の応答性とインタラクティブ性を高める方法について詳しく説明します。本連載第5回では、Telegramからのコマンドやメッセージに応答する機能を実装し、カスタムキーボードボタンを作成することで、ボットの基礎を築きました。このセグメントでは、さまざまなアクションをトリガーし、ユーザー入力に動的に応答するインラインボタンを統合することで、ボットのインタラクティブ性を高めています。
この記事は、いくつかの主要なコンポーネントを扱うように構成されています。まず、Telegramボットのインラインボタンについて、それが何であるか、その有用性、ボットインターフェイスを作成する他の方法に比べてどのような利点があるのかなどを紹介します。次に、これらのインラインボタンをMQL5で使用して、EAのユーザーインターフェイスの一部にする方法を説明します。
そこから、ユーザーがボタンを押したときにTelegramから送信されるコールバッククエリを処理する方法を説明します。これには、ユーザーのアクションを処理し、ボットがユーザーとの会話で次に取るべき適切な手順を決定することが含まれます。最後に、ボットの組み込み機能をテストして、すべてが完璧に動作することを確認します。この記事で取り上げるトピックは次のとおりです。
- Telegramボットのインラインボタンの紹介
- MQL5へのインラインボタンの統合
- ボタンアクションのコールバッククエリの処理
- インラインボタン状態の実装テスト
- 結論
この記事を読み終わる頃には、Telegram用MQL5 EA内でインラインボタンを統合および管理し、取引ボットの機能を強化して、ユーザーにとってより応答性とインタラクティブ性を高める方法を明確に理解できるようになります。では始めましょう。
Telegramボットのインラインボタンの紹介
インラインボタンは、Telegramボットメッセージ内に直接表示されるインタラクティブな要素であり、ユーザーは1回のタップでアクションを実行できます。これらのボタンは、JavaScript Object Notation (JSON)形式のインラインキーボードマークアップを使用して外観と動作を定義し、従来の方法に比べてより統合され、応答性の高いインターフェイスを提供します。メッセージに直接ボタンを埋め込むことで、ボットは追加のテキストコマンドやメッセージを必要とせず、ユーザーに合理化されたエクスペリエンスと即時の対話を提供できます。具体的にどのようなものか理解を深めるため、インラインボタンの視覚的な図を以下に示します。
従来の返信キーボードに対するインラインボタンの主な利点は、メッセージ自体の中に配置されるため、インタラクションがよりシームレスで文脈に適したものになる点です。JSON構造を使用して定義されるインラインボタンにより、複雑なユーザーインタラクションと動的な応答が可能になります。このアプローチにより、個別のメニューや追加メッセージが不要となり、混乱を軽減するとともに、即時のフィードバックとアクションが提供されるため、ユーザーエンゲージメントが向上します。これらの利点を踏まえ、次のセクションで説明するように、MetaTrader 5のMQL5における実装に取り掛かることができます。
MQL5へのインラインボタンの統合
インラインボタンをMQL5 EAに組み込むためには、メッセージのやり取りとボタンの各種状態を柔軟に管理できるフレームワークが不可欠です。これには、通話とメッセージ処理の役割を分担する複数のクラスを作成し、それぞれの機能に応じて管理する構造が必要となります。以下では、使用するクラスや関数、各要素が必要とされる理由、さらにそれらがインラインボタンを活用したボットの機能にどのように貢献するかについて詳しく解説します。まず最初のステップとしては、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. };
ここでは、Telegramから受信したメッセージに関するすべての関連情報を保持するコンテナとして機能するClass_Messageクラスを定義します。このクラスは、MQL5 EA内でメッセージデータを管理・処理するために不可欠です。
Class_Messageクラスには、メッセージの各側面をキャプチャするためのいくつかのpublic属性が含まれています。まず、done属性はメッセージが処理済みであるかどうかを示します。update_id属性とmessage_id属性には、それぞれ更新の一意識別子とメッセージの一意識別子が格納されます。また、from_id、from_first_name、from_last_name、from_username属性には、メッセージ送信者に関する情報が保持されます。さらに、chat_id、chat_first_name、chat_last_name、chat_username、chat_typeには、メッセージが送信されたチャットの詳細がキャプチャされます。message_date属性にはメッセージの日時が記録され、message_textにはメッセージの内容が格納されます。これらのクラスメンバーを定義した後、それぞれを初期化します。
//+------------------------------------------------------------------+ //| 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). }
ここでは、クラスのすべての属性にデフォルト値を設定するために、Class_Messageコンストラクタを初期化します。done属性は、メッセージが処理されていないことを示すためにfalseに設定されています。update_id、message_id、from_id、chat_idを0に初期化し、from_first_name、from_last_name、from_username、chat_first_name、chat_last_name、chat_username、およびchat_typeをNULLに設定して、これらのフィールドが空であることを示します。最後に、message_dateを0に設定し、message_textをNULLに初期化して、Class_Messageの新しいインスタンスがそれぞれデフォルト値で開始してから実際のデータが入力されるようにします。同じロジックを使用して、チャット更新の詳細を保存するチャットクラスを以下のように定義します。
//+------------------------------------------------------------------+ //| 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. };
チャットクラスを定義した後、次にコールバッククエリを処理するための追加クラスを定義する必要があります。これは、通常のメッセージとは異なり、コールバッククエリに関連付けられた特定のデータを処理するために不可欠です。コールバッククエリは、コールバックデータやクエリをトリガーしたインタラクションなど、標準メッセージには含まれない固有のデータを提供します。そのため、このクラスを使用することで、この特殊なデータを効果的にキャプチャし、管理することが可能になります。また、インラインボタンを使用したユーザーインタラクションを明確に処理することができます。この分離によって、ボタンの押下を他のメッセージやインタラクションとは別に正確に処理し、応答できるようになります。実装は以下のようになります。
//+------------------------------------------------------------------+ //| 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. };
ここでは、Telegramからのコールバッククエリに関連するデータを管理するために、Class_CallbackQueryクラスを定義します。このクラスは、インラインボタンを用いたやり取りの処理に重要な役割を果たします。クラス内では、コールバッククエリ特有の情報を格納するためのさまざまな変数が宣言されています。変数idにはコールバッククエリの一意の識別子が保持され、異なるクエリを区別することができます。from_idには、コールバックをトリガーしたユーザーのIDが格納されており、これによって送信者を識別することが可能です。また、送信者の名前情報を追跡するために、from_first_name、from_last_name、from_usernameを使用します。
message_id変数には、コールバックに関連するメッセージのIDが記録され、message_textにはそのメッセージの内容が格納されます。dataには、インラインボタンから送信されたコールバックデータが保持されており、押されたボタンに基づいて実行するアクションを決定するための重要な役割を担っています。さらに、chat_idには、応答が送信されるチャットのIDが記録され、応答が正しいチャットコンテキストに届くことを保証します。このクラスの定義と初期化の残りの部分は、基本的にEAと同様ですが、コールバッククエリを処理するためのカスタム関数を追加する必要がある点が特徴です。
//+------------------------------------------------------------------+ //| 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. }
すべての必要なクラスを定義した後、チャットの更新詳細を取得するために進むことができます。
//+------------------------------------------------------------------+ //| Function to get chat updates from Telegram | //+------------------------------------------------------------------+ int Class_Bot_EA::getChatUpdates(void) { //... return 0; //--- Return 0 to indicate successful processing of updates. }
ここでは、関数getChatUpdatesをクラスClass_Bot_EAのメソッドとして定義します。この関数はTelegram APIから更新を取得し、ボットが未処理の新しいメッセージまたはコールバッククエリのいずれかを識別します。現在のgetChatUpdatesの実装では整数値0が返され、通常は操作が正常に完了したことを示します。0を返すことで、更新の取得とその正常な処理がおこなわれたことを確認できます。次のステップとして、この関数にロジックを追加し、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. }
member_token変数が空かどうかを判定します。member_tokenがNULLの場合、ボットトークンが付与されていない状態を示します。この場合、「ERR: TOKEN EMPTY」と出力して、必要な情報が提供されていないことをユーザーに通知します。また、関数がそれ以上進まないエラー状態を示すために、-1を返します。このチェックを通過すると、次にチャットの更新を取得するためのリクエストの送信処理に進みます。
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.
まず、文字列型のout変数を定義します。このout変数は、Telegram APIから返される応答データを保持するために使用されます。次に、更新を取得するためのAPI URLを作成します。このURLを作成するには、まずTELEGRAM_BASE_URLを他のいくつかのコンポーネントと組み合わせます。「/bot」と「member_token」に保持されているボットのトークン、「/getUpdates」は、Telegramから更新を取得するためのエンドポイントです。Telegram APIは、アプリケーションが使用する主要なプラットフォームの一部であり、getUpdatesメソッドはそのプラットフォームから新しいデータを取得するための手段です。次に、APIを呼び出して、新しいデータをアプリケーションに返すようにします。その後、返された出力データを基に、さらに処理や修正をおこなうことができます。
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. } }
まず、res変数の値をチェックして、リクエストが成功したかどうかを判断します。resが0の場合、リクエストが成功したことが確認でき、次に応答の処理に進むことができます。応答の解析には、CJSONValueオブジェクトobj_jsonを作成し、このオブジェクトはNULL状態およびjv_UNDEFで初期化されます。これは、未定義の状態またはデータを受信する準備が整ったオブジェクトを示します。次に、outの内容を解析し、解析されたデータを含むオブジェクトが作成されるか、解析中にエラーが発生することになります。
デシリアライズが失敗した場合(done変数がfalseであることが示す通り)、エラーメッセージ「ERR: JSON PARSING」を出力し、問題を通知するために-1を返します。データのデシリアライズが成功した場合は、応答にokというフィールドが含まれているかを確認します。このフィールドをToBoolメソッドを使ってブール値に変換し、結果をok変数に格納します。okがfalseの場合(サーバー側で要求が成功しなかったことを意味します)、エラーメッセージ「ERR: JSON NOT OK」を出力し、-1を返します。このようにして、応答のデシリアライズとその内容の検証を適切におこないます。次に、次のロジックに従って各応答を反復処理し続けます。
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. } }
応答内の更新要素の合計数を調べるには、ArraySize関数を使用して、obj_json内のresultオブジェクトのm_elements配列内の要素数をカウントします。このカウント結果は変数totalに格納されます。次に、m_elements配列内の各更新要素を繰り返し処理するループを設定します。処理する要素はtotal個なので、ループの制御変数の範囲は0からtotalマイナス1までです。各反復処理中、ループ制御変数の現在の値iは、m_elements配列のどの要素にアクセスしているかを示します。i番目の要素を変数obj_itemに割り当てます。次に、現在の更新(obj_item)に有効な「メッセージ」が含まれているかどうかを確認します。
次に、Class_Messageのobj_msgというオブジェクトをインスタンス化します。このオブジェクトには、問題のメッセージの詳細が保持されます。obj_msgに最初に設定するのは、update_idフィールドです。これをおこなうには、obj_itemからupdate_idを抽出し、それを整数に変換して、obj_msg.update_idに格納します。次に、obj_msgでアクセスするフィールドはmessage_idです。この値は、再びobj_itemのmessageフィールドから情報を抽出し、message_idフィールドの値を整数に変換して、obj_msg.message_idに格納します。その後、obj_msgのdatetimeフィールドに、itemのdate値を設定します。その後、obj_msgのmessage_textフィールドに値を設定します。messageからtext値を抽出し、文字列に変換して、obj_msg.message_textに格納します。最後に、decodeStringCharacters関数を使用して、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.
チャットの詳細を取得した後、関連するチャットIDに基づいてメッセージを処理します。
//--- Process the message based on chat ID. member_update_id = obj_msg.update_id + 1; //--- Update the last processed update ID.
必要なメッセージの詳細を抽出して保存した後、最後に処理された更新IDを更新します。これをおこなうために、変数member_update_idにobj_msg.update_idに1を加えた値を割り当てます。これにより、次回更新を処理する際に、この更新をスキップし、次の更新から処理が再開されるようになります。最後に、ユーザーメッセージにフィルタチェックを適用する必要があります。
//--- 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. } }
ユーザーに基づいてメッセージをフィルタリングするか、フィルタリングせずにすべてのメッセージを通過させるには、まずmember_users_filterに要素が含まれているかを確認します。フィルタが空の場合(「Total == 0」)、すべてのメッセージを通過させます。フィルタに要素が含まれている場合(「Total > 0」)、送信者のユーザー名(obj_msg.from_username)がフィルタ内に存在するかをチェックします。この確認には、シーケンシャル検索メソッドSearchLinearを使用して、送信者のユーザー名がフィルタに存在するかどうかを調べます。ユーザー名が見つかった場合(検索メソッドが0以上のインデックスを返す場合)、通常どおりメッセージの処理を続行します。フィルタリングが完了した後、メッセージのチャットを検索します。送信者のユーザー名に基づくフィルタリングをおこない、特定のユーザー名のみを通過させるようにします。
メッセージのchat.member_idがチャットのobj_msg.chat_idと一致する場合、現在のインデックスを変数indexに記録し、正しいチャットが見つかった時点でループを終了します。もし一致するチャットが見つからず、indexが「-1」のままであれば、新しいチャットオブジェクトを作成し、Addメソッドでmember_chatsに追加します。その後、GetLastNodeを使用して新しく作成されたチャットオブジェクトを取得し、そのポインタをchatに保持します。obj_msg.chat_idのチャットIDをchat.member_idに設定し、現在の時刻をchat.member_timeに固定するためにTimeLocal関数を使用します。また、chat.member_stateを最初に0に設定し、新しいメッセージをchat.member_new_one.message_textに保存します。
そして、未処理であることを示すためにchat.member_new_one.doneをfalseに設定します。一致するチャットが見つかった場合(つまり、indexが-1でない場合)、GetNodeAtIndexを使って対応するチャットオブジェクトを取得し、member_timeを現在の時刻で更新します。その後、新しいメッセージをchat.member_new_one.message_textに保存し、再度未処理としてマークするためにchat.member_new_one.doneをfalseに設定します。これにより、チャットは最新のメッセージで更新され、システムはそのメッセージが未処理であることを認識できるようになります。次に、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. //... }
まず、現在の更新(obj_item)にコールバッククエリが含まれているかどうかを確認します。そのためには、callback_queryフィールドの型(m_type)がjv_UNDEFと等しくないかどうかを確認します。これにより、更新内にコールバッククエリが存在することが保証されます。この条件が満たされると、obj_cb_queryという名前の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.
まず、コールバッククエリ自体の詳細から始めます。コールバッククエリのIDは、callback_queryフィールドから取得されます。これを文字列形式に変換するためにToStrメソッドを使用し、結果をobj_cb_query.idに格納します。次に、送信者のIDを取得します。これはfromフィールドから取得し、ToIntメソッドで整数に変換してobj_cb_query.from_idに格納します。その後、送信者の名前(名)をfromフィールドから取得し、文字列に変換してobj_cb_query.from_first_nameに格納します。また、名前に含まれる可能性のある特殊文字を処理するためにdecodeStringCharacters関数を使用します。
同様に、送信者の姓をfromフィールドから取得し、文字列に変換してobj_cb_query.from_last_nameに格納します。姓にも特殊文字が含まれている可能性があるため、再度decodeStringCharactersを適用します。送信者のユーザー名も同じ方法で抽出し、文字列に変換してobj_cb_query.from_usernameに格納します。ユーザー名に特殊文字がある場合も、decodeStringCharactersを使用して適切に処理します。
次に、コールバッククエリに関連するメッセージの詳細に注目します。messageフィールドからメッセージIDを取得し、それを整数に変換してobj_cb_query.message_idに格納します。同時に、メッセージテキストも抽出して文字列に変換し、obj_cb_query.message_textに格納します。メッセージ内の特殊文字もすべてデコードします。その後、コールバックデータに焦点を当てます。コールバックデータを抽出し、それを文字列に変換してobj_cb_query.dataに保存します。コールバックデータも特殊文字が含まれている場合があるため、decodeStringCharactersを使って適切に処理します。
最後に、コールバッククエリに関連するメッセージが送信されたチャットのIDを取得し、それを整数に変換してobj_cb_query.chat_idに格納します。この時点で、コールバッククエリに関する完全な情報セット、つまり送信者のID、名前、ユーザー名、メッセージID、メッセージテキスト、コールバックデータ、チャット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.
ここでは、ProcessCallbackQuery関数を呼び出し、obj_cb_queryオブジェクトを引数として渡します。この関数は、コールバッククエリを処理し、ユーザー情報、チャットID、メッセージテキスト、コールバックデータなど、以前に収集した詳細情報を処理する役割を担います。この関数を呼び出すことで、コールバッククエリがその特定の内容に基づいて適切に処理されることが保証されます。
コールバッククエリを処理した後、obj_itemフィールドからupdate_idを取得し、それを整数に変換して1を加算することで、最後に処理された更新IDを更新します。この値は、処理された最新の更新を追跡するmember_update_idに保存されます。この手順により、将来の反復で同じコールバッククエリを再処理することがなくなり、更新の進行状況を効率的に追跡できます。最後に、最初のメッセージを処理した後、再処理を回避するためにメッセージを処理済みとしてマークする必要があります。
member_first_remove = false; //--- After processing the first message, mark that the first message has been handled.
最初のメッセージを処理した後、変数member_first_removeをfalseに設定します。これは、最初のメッセージが処理され、特別な処理が必要であれば完了したことを示します。この手順を実行する理由は、最初のメッセージが再度処理されないことを保証することです。member_first_removeをfalseにすることで、最初のメッセージが未処理であることに依存する将来のロジックが実行されなくなります。
チャットメッセージとコールバッククエリの取得と保存を担当するソースコードの全文は以下の通りです。
//+------------------------------------------------------------------+ //| 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. }
チャットの更新を取得したら、次は応答を処理する必要があります。これは次のセクションで扱います。
ボタンアクションのコールバッククエリの処理
このセクションでは、受信したメッセージを処理し、特定のコマンドに基づいてインラインボタンで応答します。最初におこなうべきことは、ユーザーが送信した初期化コマンドまたはメッセージを処理することです。その後、インラインボタンをインスタンス化し、コールバッククエリを取得します。ユーザーから最初にコマンドを受け取らずにインラインキーボードを提供することはできないため、この手順をスキップすることはできません。これが、私たちのロジックの進め方です。
#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 } } } }
この例では、システムに入ってくるユーザーメッセージを処理するためにProcessMessages関数を設定します。この関数が最初におこなうのは、member_chatsに保存されたすべてのチャットを反復処理することです。各チャットについて、GetNodeAtIndex(i)を呼び出して現在のチャットに対応するチャットオブジェクトを取得します。現在のチャットが取得できたら、member_new_oneのメッセージがすでに処理されているかどうかを確認し、処理されていない場合はそれを処理済みとしてマークします。
次に、chat.member_new_one.message_textを使ってメッセージの内容を抽出し、ユーザーが「Start」や「/start」、「Help」や「/help」などのコマンドを送信したかを判断します。これらのコマンドを受け取った場合、ユーザーを歓迎し、「インラインボタンで操作できます」というメッセージを返します。その後、ユーザーのメニューオプションとして機能するインラインコールバックボタンを定義します。ボタンのcallback_dataフィールドを使用して、それがBTN_MENUに関連していることを示します。ボタンはJSONオブジェクトとしてフォーマットされ、buttons変数に格納されます。
最後に、sendMessageToTelegram関数が呼び出され、ウェルカムメッセージとカスタムインラインキーボードがユーザーに送信されます。この関数はchat.member_id、message、customInlineKeyboardMarkup関数によって生成されたボタンマークアップの3つのパラメータを受け取ります。メッセージとインラインボタンがユーザーに送信され、ユーザーは一般的なユーザーがTelegramボットと対話するのと同じように、インラインボタンと対話できるようになります。インラインキーボードについては初心者の方も多いため、そのロジックに注力していきましょう。
string buttons = "[[{\"text\": \"Provide Menu\", \"callback_data\": \""+BTN_MENU+"\"}]]";
詳細な内訳
外側の括弧:文字列全体は二重引用符(" ")で囲まれます。これは、多くのプログラミング言語で文字列リテラルを定義する際に一般的な方法です。この文字列内には「[[ ... ]]」という部分が含まれています。これらの括弧は、インラインキーボードの構造を定義するために使用されます。
- 最初の括弧 [ ... ] は、キーボードの行の配列を表します。各行には1つ以上のボタンが含まれます。
- 2番目の括弧 [ ... ] は、その配列内の行を表します。この場合、行は1つだけです。
ボタンの定義
2つ目の括弧の中には、オブジェクト{"text":"Provide Menu", "callback_data": " + BTN_MENU + "}があります。このオブジェクトは1つのボタンを定義します。
- "text":このキーはボタンのラベル(「Provide Menu」)を指定します。ユーザーがボタンを見たときにボタン上に表示されるテキストです。
- "callback_data":このキーは、ボタンがクリックされたときにボットに送り返されるデータを指定します。この場合、値はBTN_MENUで、これはコードの他の場所で定義した定数です。これにより、ボットはどのボタンがクリックされたかを認識し、それに応じて適切に応答できます。
要素の組み合わせ
BTN_MENU定数は、文字列連結を使用してJSON文字列に挿入されます。これにより、ボタンのコールバックデータを動的に含めることができます。たとえば、BTN_MENUがBTN_MENUの場合、結果のJSONは次のようになります。[{"text":"Provide Menu", "callback_data":"BTN_MENU"}]。
最終フォーマット
コード内で使用されるボタン文字列の最終的な形式は、次のようになります。"[ [{ "text":"Provide Menu", "callback_data":"BTN_MENU" }] ]"。この書式は、キーボード上に1つの行があり、その行に1つのボタンがあることを指定します。
Telegram APIはこのJSON構造を受け取ると、それを1つのボタンを持つインラインキーボードとして解釈します。ユーザーがこのボタンをクリックすると、ボットはコールバッククエリでコールバックデータBTN_MENUを受け取り、それを使用して適切な応答を決定できます。この構造では、カスタム関数を使用してインラインボタンを作成しています。そのロジックは次の通りです。
//+------------------------------------------------------------------+ //| 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); }
customInlineKeyboardMarkup関数は、Telegramメッセージ用のカスタムインラインキーボードマークアップを作成します。この関数では、インラインボタンを定義するJSON構造を含む文字列パラメータbuttonsを受け取り、Telegramがインラインキーボードをレンダリングするために必要なJSONオブジェクトを構築します。まず、inline_keyboardキーを使用してJSON構造を形成します。次に、UrlEncode関数を使用して、buttons文字列に存在する可能性のある特殊文字を処理します。このエンコード手順は非常に重要です。この手順がないと、ボタン定義の特殊文字で問題が発生する可能性があるためです。エンコードされたボタン文字列を追加した後、JSONオブジェクトを閉じます。結果の文字列は、インラインキーボードマークアップの有効なJSON表現です。この文字列を返してTelegram APIに送信できるようにすると、メッセージ内のインラインキーボードが対話的にレンダリングされます。プログラムを実行すると、次のような出力が得られます。
成功したことがわかります。インラインボタンは作成しました。ただし、クリックにはまだ応答できないため、受信したコールバッククエリをキャプチャし、クリックにそれぞれ応答する必要があります。これを実現するには、クエリデータを取得する関数を作成します。
//+------------------------------------------------------------------+ //| 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 }
ProcessCallbackQuery関数は、Telegramから送信されるコールバッククエリの詳細を管理します。この関数は、コールバックに関連するすべての情報を保持するClass_CallbackQueryのインスタンスで動作します。まず、コールバッククエリのIDを記録します。これはクエリの一意な識別子であり、クエリを追跡・管理するために必要不可欠です。次に、この関数はmember_tokenを記録します。このトークンの役割は、どのボットまたはメンバーがコールバックを処理しているかを示し、正しいボットのみがクエリを処理していることを確認するためです。
次に、送信者のファーストネームとラストネームをそれぞれcb_query.from_first_nameとcb_query.from_last_nameを使って記録します。これにより、インラインボタンを押したユーザーのIDがわかり、将来的にそのユーザーに話しかける必要がある場合に個別対応が可能となります。IDに関しては、cb_query.from_usernameを使って送信者のユーザー名も記録します。これにより、将来必要に応じてそのユーザーに直接話しかける方法を得ることができます。送信者のIDを記録した後、cb_query.message_idを使ってコールバックに関連付けられたメッセージのIDを記録します。このIDを元に、ボタンが押された特定のメッセージを特定できます。
さらに、cb_query.message_textを使ってメッセージのテキストをログに記録します。これにより、ボタンがクリックされた際のメッセージに関するコンテキストが得られます。また、cb_query.dataを使用してコールバックデータをログに記録します。このデータは、ボタンのクリック時に返されるもので、ユーザーの操作に基づいて実行するアクションを決定するために使用されます。これらの詳細をログに記録することで、コールバッククエリの包括的なビューが得られます。これにより、デバッグが容易になり、ボットとユーザーの操作をより深く理解することができます。プログラムを実行すると、取引ターミナルで次の出力が得られます。
情報が正常に取得できることが確認できたので、次はクリックされたボタンを確認し、それに応じた適切な応答を生成することができます。この場合、メニューボタンのアクションから得られたコールバックデータを使用します。まず、ボタンの定数を定義します。コードが理解しやすくなるように、詳細なコメントを追加しました。
#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
関数を定義したら、応答を取得する手順に進みます。
//--- 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 }
ここでは、コールバックデータに基づいてコールバッククエリへの応答を管理します。まず、ユーザーに返信するメッセージを格納する文字列変数response_textを初期化します。次に、コールバッククエリ(cb_query.data)のcallback_dataが定数BTN_MENUと一致するかどうかを確認します。一致した場合、response_textを「+BTN_MENU+」に設定し、ボタンが押されたことを認識します。クリックされたボタンの識別子を含めて応答を作成し、Print関数を使用してこの応答を記録し、送信内容を追跡します。
次に、sendMessageToTelegram関数を使用して、cb_query.chat_idで識別されるチャットにresponse_textメッセージを送信します。この時点ではインラインキーボードなしで単純なテキストメッセージを送信しているため、3番目のパラメータはNULLとなり、追加のキーボードマークアップが含まれていないことを示します。
最初のメッセージを送信した後、「Information」というテキストで新しいメッセージを用意し、ユーザーに様々なオプションを提供する。次に、buttons文字列を使用してJSONのような構造でインラインボタンを定義します。この構造には、[Get Expert's Name]、[Get Account Information]、[Get Current Market Quotes]、[More]、[Screenshots]、[Cancel]などのラベルが付いたボタンが含まれます。各ボタンにはBTN_NAME、BTN_INFO、BTN_QUOTES、BTN_MORE、BTN_SCREENSHOT、EMOJI_CANCELなどの特定のcallback_data値が割り当てられ、どのボタンが押されたかを識別するのに役立ちます。
最後に、sendMessageToTelegram関数を使用して、この新しいメッセージをインラインキーボードとともに送信します。インラインキーボードは、customInlineKeyboardMarkup関数によってJSONにフォーマットされ、Telegramがボタンを正しく表示できるようにします。このアプローチにより、Telegramインターフェース内で直接さまざまなオプションを提供することで、ユーザーをインタラクティブに引き付けることができます。コンパイルすると、次の結果が得られます。
うまくいきました。次に、特定のインラインボタンから受け取ったコールバックのクエリデータに応答する必要があります。まずは、プログラム名の取得を担当するものから始めましょう。
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 }
このセクションでは、callback_dataがBTN_NAMEに等しい特定のコールバッククエリを管理します。まず、response_text変数に応答テキストを設定します。callback_dataがBTN_NAMEと一致する場合、response_textを「"You clicked " + BTN_NAME + "!"」に設定します。これにより、ボタンが押されたことが認識され、クリックされたボタンの識別子が含まれます。次に、Print関数を使用してこの応答を出力し、ユーザーに送信される内容を確認します。
次に、ボットが制御しているEAファイルの詳細を伝える新しいメッセージを作成します。このメッセージは、「The file name of the EA that I control is:\n」という語句で始まり、__FILE__で表される現在のソースファイル名が追加され、最後に「Enjoy」という親しみのある言葉で締めくくられます。興味深い点は、メッセージが「\xF50B」という文字で始まることです。これはアイコンや装飾、またはボットに代わって読者を驚かせるための手法です。
最後に、sendMessageToTelegram関数を呼び出して、cb_query.chat_idに対応するチャットにメッセージを送信します。この時、3番目のパラメータにはNULLを渡します。これにより、このメッセージにはインラインキーボードが付属しないことが示されます。ボタンをクリックすると、次の応答が返されます。
うまくいきました。次に、口座情報や市場価格の相場など、他のボタンの応答性を実現するために、同様のアプローチを使用します。
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 }
コンパイルすると、次のような結果が得られます。
うまくいきました。次に、[More]インラインボタンも処理します。ここまでで、インターフェイスやチャットフィールドがメッセージで乱雑になっていないことがわかります。すっきりしていて、インラインボタンを効率的に再利用しています。
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 }
ここでは、コールバックデータがBTN_MOREであるコールバッククエリを処理します。まず、response_text変数に格納される応答メッセージを準備します。コールバックデータがBTN_MOREと一致する場合、response_textを「"You clicked "+BTN_MORE+"!"」に設定します。これにより、ボタンが押されたことが通知され、クリックされたボタンの識別子が含まれます。この応答は、送信内容を追跡するためにPrint関数を使用してログに記録されます。
次に、ユーザーに追加のオプションから選択するよう促す新しいメッセージを作成します。message変数は「Choose More Options Below:\n」で始まり、その後に「Trading Operations」が続きます。これは、取引に関連するオプションセットのタイトルとして機能します。次に、buttons文字列でJSONのような構造を使用して、これらの追加オプションのインラインボタンを定義します。この構造には次の要素が含まれます。
- 絵文字EMOJI_UPとそれに対応するcallback_dataがEMOJI_UPであるボタン
- さまざまな取引操作用のボタンの行(カッコ内はcallback_data値):[Buy](BTN_BUY)、[Close](BTN_CLOSE)、[Next](BTN_NEXT)
最後に、sendMessageToTelegram関数を使用して、このメッセージをインラインキーボードとともにcb_query.chat_idで識別されるチャットに送信します。インラインキーボードマークアップは、customInlineKeyboardMarkup関数によってJSONにフォーマットされます。このボタンをクリックすると、別の拡張ボタンが表示されます。これは下図の通りです。
予想通りの展開でした。次は、新しく登場するボタンの作業です。まず、上向きの絵文字ボタンです。これをクリックすると、前のメニュー、つまりメインメニューに戻ります。
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 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); }
ここでは、これまで使用してきたロジックと同様のアプローチが実装されていますが、インラインボタンを削除する別の関数が追加されています。
//+------------------------------------------------------------------+ //| 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 }
このセクションでは、removeInlineButtons関数を定義します。この関数の目的は、メッセージの返信マークアップを変更して、以前に送信されたメッセージからインラインボタンを削除することです。この関数は、3つのパラメータを受け取ります:memberToken(ボットの認証トークン)、chatID(メッセージが送信されたチャットのID)、およびmessageID(インラインボタンを含むメッセージのID)。まず、TelegramのeditMessageReplyMarkupメソッドのAPIエンドポイントURLを作成します。これは、TELEGRAM_BASE_URLと「/bot」とmemberTokenを組み合わせることでおこないます。このURLは、Telegramのサーバーとの通信に使用されます。
次に、API呼び出しに必要なパラメータを含むparams文字列を指定します。これにはchat_idパラメータが含まれます。その値を取得するために、chatID変数を整数から文字列に変換します。同様に、message_idパラメータも文字列に変換します。最後に、空のreply_markupフィールドを送信し、インラインボタンを削除するようにAPIに指示します。このフィールドの値は空のJSON文字列で、emptyInlineKeyboard変数の値をURLエンコードして取得します。
パラメータを設定したら、サーバーから返される応答を保持するresponse変数を宣言し、postRequest関数を呼び出してAPIリクエストをTelegramに送信します。postRequest関数は、指定されたURLとパラメータを使用してタイムアウト(WEB_TIMEOUT)を設定し、リクエストを送信します。リクエストが成功すると、インラインボタンが削除されたメッセージが返され、ボタンの状態が実質的にリセットされます。コールバックデータが認識されない場合、クリックされたボタンが不明であることを示す出力が返されます。これは、ボタンが認識されなかったことを意味します。
else { response_text = "Unknown button!"; //--- Prepare response text for unknown buttons Print("RESPONSE = ", response_text); //--- Log the response }
キャンセルボタンをクリックすると、次の出力が表示されます。
うまくいきました。コールバッククエリの処理を担当するソースコードの全文は以下の通りです。
#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 }
簡単にまとめると、特定のボタンの押下に応答し、ユーザーを適切なインラインキーボードオプションに誘導することで、コールバッククエリを管理します。これにより、状況に応じて適切なメッセージとオプションが提供され、インタラクションが強化されます。次に、統合をテストして、これらの機能が正常に動作し、インタラクションが正しく処理されることを確認します。
インラインボタン状態の実装テスト
このセクションでは、インラインボタンがTelegramボットとMQL5 EAとどのように連携するかを確認します。具体的には、ユーザーのボタン操作をシミュレートし、ボットがコールバッククエリを正しく処理できるかを確認します。また、インラインボタンが正しく表示、削除、または更新されるかを評価します。さらに、この統合がどのように機能するかを示すビデオを作成し、インラインボタンの操作に対するボットの動作をステップごとに解説しています。これにより、セットアップがリアルタイムで期待通りに動作することが確認できます。以下はそのイラストです。
ボタンのインタラクションとコールバッククエリをテストして、ボットがユーザー入力に正確に対応し、インラインボタンの状態が必要に応じて更新またはリセットされることを確認しました。これにより、非線形のインタラクションスタイルが提供され、エンゲージメントが強化され、Telegramを通じてボットを制御する際のより効率的なエクスペリエンスが提供されます。
結論
結論として、Telegramボットにコールバッククエリとインラインボタンを導入し、テストをおこないました。これにより、ボットはユーザーの入力に対してカスタマイズされたメッセージで応答し、インラインキーボードを通じてインタラクティブなオプションを提供できるようになりました。メニューへのアクセス、専門家情報の取得、取引関連のコマンド実行など、リアルタイムで使いやすいボタンが追加され、ユーザーエクスペリエンスが向上しました。
システムをテストした結果、各コールバッククエリが正しく処理され、ユーザーに必要なフィードバックが提供されることが確認できました。これにより、ボットは意図した通りに機能し、会話の質とユーザーの関心、使いやすさを維持できます。インラインボタンは、チャットフィールドを整理し、より効率的に機能することがわかりました。この記事が詳細かつわかりやすく、皆さんの参考になれば幸いです。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/15823





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索