PR

【MQL4,MQL5共通】Discord通知ソースコード

オリジナル関数

はじめに

2025年3月にLINE Notify APIのサービスが終了することが発表され、多くの方々が代替手段を探していることと思います。今回は、その代替としてDiscord通知のソースコードをご紹介します。

このコードの特徴は以下の通りです:

  • WebRequestの許可リストへの登録が不要
  • 画像だけでなく、CSVなど様々なファイル形式の添付が可能
  • GET/POSTリクエストに対応した柔軟な実装

Discord Webhookの準備

  1. Discordサーバーを開き、通知を送信したいチャンネルの設定を開きます
  2. 「連携サービス」→「ウェブフック」を選択
  3. 「新しいウェブフック」を作成
  4. 生成されたWebhook URLをコピーしておきます

コードの実装

まず、以下のコードをMT4/MT5のエディタにコピーしてください。

#property copyright "FX WAN."
#property link      "https://twitter.com/wonderfulfxlife"
#property version   "1.00"
#property strict

// WebhookのURLを設定する入力パラメータ
input string WebhookURL = "https://discord.com/api/webhooks/abcdefg";

//+------------------------------------------------------------------+
//| スクリプトが開始された時に呼ばれる関数                              |
//+------------------------------------------------------------------+
void OnStart()
  {
// テストメッセージとファイルをDiscordに送信
   SendDiscordNotification("🥺", WebhookURL, "test.png");
  }

//+------------------------------------------------------------------+
//| Discordに通知を送る関数
//| @param message 送信するメッセージ
//| @param webhook DiscordのWebhook URL
//| @param fileName 添付するファイル名(省略可能引数。添付はFilesフォルダ内のファイルのみ)
//+------------------------------------------------------------------+
void SendDiscordNotification(string message, string webhook, string fileName = "")
  {
// DiscordのWebhook URL
   string url = webhook;

// マルチパートフォームデータの境界文字列
   string boundary = "---------------------------" + IntegerToString(TimeCurrent());

// リクエストのヘッダー
   string headers = "Content-Type: multipart/form-data; boundary=" + boundary + "\r\n";

// POSTデータの準備
   string body;
   body += "--" + boundary + "\r\n";
   body += "Content-Disposition: form-data; name=\"content\"\r\n\r\n";
   body += message + "\r\n";

   int size = 0;
   uchar post_data[];
   if(fileName != "")
     {
      // 画像を送るための情報を追加
      body += "--" + boundary + "\r\n";
      body += "Content-Disposition: form-data; name=\"imageFile\"; filename=\"" + fileName + "\"\r\n";
      body += "Content-Type: image/png\r\n\r\n";
      size = StringToCharArray(body, post_data, 0, WHOLE_ARRAY, CP_UTF8);

      // 画像ファイルを開く
      uchar img[];
      int handle = FileOpen(fileName, FILE_BIN | FILE_READ);
      if(handle != INVALID_HANDLE)
        {
         // 画像データを読み込む
         FileReadArray(handle, img);
         FileClose(handle);
        }
      // 画像データを追加
      size += ArrayCopy(post_data, img, size - 1);
      // データの終わりを示す
      StringToCharArray("\r\n" + "--" + boundary + "--\r\n", post_data, size - 1);
     }
   else
     {
      // 文字のみを送るための情報を追加
      size = StringToCharArray(body, post_data, 0, WHOLE_ARRAY, CP_UTF8);
      // データの終わりを示す
      StringToCharArray("--" + boundary + "--\r\n", post_data, size - 1);
     }

// POSTデータの最終形成
   body += "--" + boundary + "--\r\n";

// DiscordにPOSTリクエストを送信
   int timeout = 5000;
   char result[];
   string headers_res;
   int res = WebRequest2("POST", url, headers, timeout, post_data, result, headers_res);

// エラーの確認と結果の出力
   if(res == -1)
     {
      Print("HTTPリクエストエラー: ", GetLastError());
     }
   else
     {
      string res_str = CharArrayToString(result);
      Print("Discord通知が送信されました。レスポンス: ", res_str);
     }
  }


//+------------------------------------------------------------------+
//| GETまたはPOSTリクエストを実行し、データを取得  ワンダフルFXライフ
//+------------------------------------------------------------------+
#define INTERNET_DEFAULT_HTTPS_PORT 443
#define INTERNET_DEFAULT_HTTP_PORT 80
#define INTERNET_SERVICE_HTTP 3
#define HTTP_QUERY_STATUS_CODE 19
#define HTTP_QUERY_RAW_HEADERS_CRLF 21
#define INTERNET_FLAG_SECURE 0x00800000
#define INTERNET_FLAG_PRAGMA_NOCACHE 0x00000100
#define INTERNET_FLAG_KEEP_CONNECTION 0x00400000
#define INTERNET_FLAG_RELOAD 0x80000000
#define INTERNET_OPTION_CONNECT_TIMEOUT 2
#define INTERNET_OPTION_SEND_TIMEOUT 5
#define INTERNET_OPTION_RECEIVE_TIMEOUT 6
#import "wininet.dll"
int InternetOpenW(string userAgent, int accessType, string proxyServer, string bypassProxyFor, int flags);
int InternetOpenUrlW(int internetSession, string url, string header, int headerLength, int flags, int context);
int InternetConnectW(int sessionHandle, string serverUrl, int serverPort, string userCredentials, string userPassword, int serviceType, int connectionFlags, int context);
int HttpOpenRequestW(int connectionHandle, string httpVerb, string objectUrl, string httpVersion, string referrer, string acceptTypes, uint requestFlags, int context);
bool HttpSendRequestW(int hRequest, string lpszHeaders, int dwHeadersLength, uchar &lpOptional[], int dwOptionalLength);
bool InternetReadFile(int hFile, uchar &lpBuffer[], int dwNumberOfBytesToRead, int &lpdwNumberOfBytesRead);
bool InternetCloseHandle(int hInternet);
bool HttpQueryInfoA(int hRequest, int dwInfoLevel, uchar &lpBuffer[], int &lpdwBufferLength, int &lpdwIndex);
bool InternetSetOptionW(int hInternet, int dwOption, string lpBuffer, int dwBufferLength);
#import
int WebRequest2(const string method, const string url, const string headers, int timeout, char & data[], char & result[], string & result_headers)
  {
   int secure = 0;
   int port = INTERNET_DEFAULT_HTTP_PORT;
   if(StringFind(url, "https") >= 0)
     {
      secure = INTERNET_FLAG_SECURE;
      port = INTERNET_DEFAULT_HTTPS_PORT;
     }
//URLからホストとエンドポイントの抽出
   string host = "", endpoint = "";
   int response = -1;
   int http_pos = StringFind(url, "//");
   if(http_pos >= 0)
     {
      int next_slash = StringFind(url, "/", http_pos + 2);
      if(next_slash < 0)
         next_slash = StringLen(url);
      host = StringSubstr(url, http_pos + 2, next_slash - http_pos - 2);
      endpoint = StringSubstr(url, next_slash);
     }
// インターネットセッションを開始
   int session = InternetOpenW("Mozilla/5.0", 0, "", "", 0);
   if(!session)
     {
      string error = "エラー:インターネットセッションを開けません";
      StringToCharArray(error, result);
      return(response);
     }

// タイムアウトの設定
   string timeoutStr = IntegerToString(timeout);
   bool timeout_res = InternetSetOptionW(session, INTERNET_OPTION_CONNECT_TIMEOUT, timeoutStr, sizeof(timeout));
   timeout_res = InternetSetOptionW(session, INTERNET_OPTION_SEND_TIMEOUT, timeoutStr, sizeof(timeout));
   timeout_res = InternetSetOptionW(session, INTERNET_OPTION_RECEIVE_TIMEOUT, timeoutStr, sizeof(timeout));

   if(method == "GET")
     {
      // URLを開く
      int connect = InternetOpenUrlW(session, url, headers, StringLen(headers), secure, 0);
      if(!connect)
        {
         InternetCloseHandle(session);
         string error = "エラー:URLを開けません";
         StringToCharArray(error, result);
         return(response);
        }

      //レスポンス確認
        {
         int bufferLength = 1024;
         uchar result_array[];
         string result_str = "";
         ArrayResize(result_array, bufferLength);
         int index = 0;
         // HTTPステータスコードを取得
         if(HttpQueryInfoA(connect, HTTP_QUERY_STATUS_CODE, result_array, bufferLength, index))
           {
            for(int i = 0; i < bufferLength; i++)
              {
               uchar ch[1];
               ch[0] = result_array[i];
               if(ch[0] != NULL)
                  result_str += CharArrayToString(ch);
               else
                  result_str += "\n";
              }
           }
         response = (int)result_str;
        }

      char receive[1024];
      int byteSize = 0;
      int totalBytesRead = 0;
      // レスポンスデータを読み取る
      while(InternetReadFile(connect, receive, 1024, byteSize))
        {
         if(byteSize <= 0)
            break;
         ArrayResize(result, totalBytesRead + byteSize);
         for(int i = 0; i < byteSize; ++i)
           {
            result[totalBytesRead + i] = receive[i];
           }
         totalBytesRead += byteSize;
        }

      InternetCloseHandle(connect);
     }
   else
      if(method == "POST")
        {
         // ホストに接続
         int connect = InternetConnectW(session, host, port, "", "", INTERNET_SERVICE_HTTP, 0, 0);
         if(!connect)
           {
            InternetCloseHandle(session);
            string error = "エラー:接続できません";
            StringToCharArray(error, result);
            return(response);
           }

         // HTTPリクエストを開く
         int hRequest = HttpOpenRequestW(connect, method, endpoint, "HTTP/1.1", "", "", secure | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_RELOAD | INTERNET_FLAG_PRAGMA_NOCACHE, 0);
         if(!hRequest)
           {
            InternetCloseHandle(connect);
            InternetCloseHandle(session);
            string error = "エラー:HTTPリクエストを開けません";
            StringToCharArray(error, result);
            return(response);
           }

         if(!HttpSendRequestW(hRequest, headers, StringLen(headers), data, ArraySize(data)))
           {
            InternetCloseHandle(hRequest);
            InternetCloseHandle(connect);
            InternetCloseHandle(session);
            string error = "エラー:HTTPリクエストの送信に失敗しました。";
            StringToCharArray(error, result);
            return(response);
           }

         //レスポンス確認
           {
            int bufferLength = 1024;
            uchar result_array[];
            string result_str = "";
            int index = 0;
            ArrayResize(result_array, bufferLength);
            // HTTPステータスコードを取得
            if(HttpQueryInfoA(hRequest, HTTP_QUERY_STATUS_CODE, result_array, bufferLength, index))
              {
               for(int i = 0; i < bufferLength; i++)
                 {
                  uchar ch[1];
                  ch[0] = result_array[i];
                  if(ch[0] != NULL)
                     result_str += CharArrayToString(ch);
                  else
                     result_str += "\n";
                 }
              }
            response = (int)result_str;
           }
         //リクエストヘッダー取得
           {
            int bufferLength = 1024;
            uchar result_array[];
            string result_str = "";
            ArrayResize(result_array, bufferLength);
            int index = 0;
            // HTTPステータスコードを取得
            if(HttpQueryInfoA(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, result_array, bufferLength, index))
              {
               for(int i = 0; i < bufferLength; i++)
                 {
                  uchar ch[1];
                  ch[0] = result_array[i];
                  if(ch[0] != NULL)
                     result_str += CharArrayToString(ch);
                  else
                     result_str += "\n";
                 }
              }
            result_headers = result_str;
           }

         char receive[1024];
         int byteSize = 0;
         int totalBytesRead = 0;
         // レスポンスデータを読み取る
         while(InternetReadFile(hRequest, receive, 1024, byteSize))
           {
            if(byteSize <= 0)
               break;
            ArrayResize(result, totalBytesRead + byteSize);
            for(int i = 0; i < byteSize; ++i)
              {
               result[totalBytesRead + i] = receive[i];
              }
            totalBytesRead += byteSize;
           }

         InternetCloseHandle(hRequest);
         InternetCloseHandle(connect);
        }
// インターネットセッションを閉じる
   InternetCloseHandle(session);
   return(response);
  }
//+------------------------------------------------------------------+

使用方法

  1. 上記のコードをインジケーターやEAに追加します
  2. WebhookURLパラメータに、先ほどコピーしたDiscord Webhook URLを設定します
  3. 以下のように関数を呼び出すことで通知が送信できます:
// テキストのみの通知
SendDiscordNotification("トレード開始:USDJPY買い", WebhookURL);

// 画像付きの通知
SendDiscordNotification("トレード結果", WebhookURL, "chart.png");

// CSVファイルの添付
SendDiscordNotification("トレードログ", WebhookURL, "trades.csv");

技術的な特徴

WebRequest2関数について

このコードの特筆すべき点は、WinAPIを使用した独自のWebRequest2関数の実装です。通常のWebRequest関数では必要となるURLの許可リスト登録が不要となり、設定の手間を大幅に削減できます。

【MQL4&MQL5】新WebRequest2関数(DLLを使用した画像付きLINE通知ソースコード)|ワン('ω')FXフリーランス
WebRequestを使用しないでHTTPリクエストがしたいけどWinAPI関数を使用するのはちょっとよくわからない という迷える諸君('ω') お待たせしました、お待たせしすぎたかもしれません WebRequestの引数をそのままに、Wi...

マルチパートフォームデータの対応

LINE通知では画像ファイルのみの対応でしたが、このDiscord実装では様々なファイル形式に対応しています。マルチパートフォームデータの実装により、画像、CSV、その他のファイル形式を柔軟に添付できます。

注意点

  • ファイルを添付する場合は、Files フォルダ内のファイルのみが対象となります
  • 大容量のファイル転送には適していない場合があります
  • HTTPS通信を使用するため、セキュリティは確保されています

ダウンロード

ソースファイルはこちらに置きましたので上記のコードで不安な方は下記からダウンロードしてください。

note ご指定のページが見つかりません
タイトルとURLをコピーしました