😽

MQL4DLLDidcord

に公開
//+------------------------------------------------------------------+
//|                                    Discord Notification EA.mq4    |
//|                                          Copyright 2025          |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025"
#property link      ""
#property version   "1.00"
#property strict

// 外部パラメータ
extern string   DiscordWebhookURL = "https://discord.com/api/webhooks/your-webhook-id/your-webhook-token"; // Discord Webhook URL
extern int      ScreenshotInterval = 300; // スクリーンショット間隔(秒)
extern string   MessageText = "MT4からの画像です"; // 送信メッセージ
extern bool     DebugMode = true; // デバッグモード

// グローバル変数
datetime g_lastScreenshotTime = 0;
string g_terminalDataPath = "";

// DLL関連の定義
#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

// DLLのインポート
#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

#import "kernel32.dll"
uint GetLastError(void);
uint GetTickCount(void);
#import

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    // 初期化時に時間をリセット
    g_lastScreenshotTime = 0;
    
    // ターミナルデータパスを取得
    g_terminalDataPath = TerminalInfoString(TERMINAL_DATA_PATH);
    
    // DLL使用許可をチェック
    if(!IsDllsAllowed())
    {
        Print("[ERROR] DLL使用が許可されていません。Tools->Options->Expert Advisorsで'Allow DLL imports'を有効にしてください。");
        MessageBox("Tools->Options->Expert AdvisorsでDLLインポートを有効にしてください", "設定が必要です", MB_OK|MB_ICONWARNING);
        return INIT_FAILED;
    }
    
    // 初期化情報を表示
    Print("Discord通知EAの初期化が完了しました");
    Print("データパス: " + g_terminalDataPath);
    Print("メッセージテキスト: " + MessageText);
    
    // テストメッセージを送信
    string testMessage = "Discord通知EAが初期化されました";
    if(DebugMode) Print("[DEBUG] テストメッセージを送信: " + testMessage);
    
    SendDiscordNotification(testMessage, DiscordWebhookURL);
    
    // タイマーを開始
    EventSetTimer(60); // 1分ごとにチェック
    
    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // タイマーを停止
    EventKillTimer();
    
    Print("Discord通知EAが停止しました。理由コード: " + IntegerToString(reason));
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // 現在の時間を取得
    datetime currentTime = TimeLocal();
    
    // スクリーンショット間隔をチェック
    if(currentTime - g_lastScreenshotTime >= ScreenshotInterval)
    {
        // スクリーンショットを撮影して送信
        TakeAndSendScreenshot();
        
        // 最後のスクリーンショット時間を更新
        g_lastScreenshotTime = currentTime;
    }
}

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
    // 現在の時間を取得
    datetime currentTime = TimeLocal();
    
    // スクリーンショット間隔をチェック
    if(currentTime - g_lastScreenshotTime >= ScreenshotInterval)
    {
        // スクリーンショットを撮影して送信
        TakeAndSendScreenshot();
        
        // 最後のスクリーンショット時間を更新
        g_lastScreenshotTime = currentTime;
    }
}

//+------------------------------------------------------------------+
//| スクリーンショットを撮影して送信する関数                          |
//+------------------------------------------------------------------+
void TakeAndSendScreenshot()
{
    // 現在の日時を取得してファイル名に使用
    string timestamp = TimeToStr(TimeLocal(), TIME_DATE|TIME_SECONDS);
    StringReplace(timestamp, ":", "-");
    StringReplace(timestamp, " ", "_");
    
    // スクリーンショットのファイル名を作成
    string filename = "MT4_Screenshot_" + timestamp + ".png";
    
    if(DebugMode) Print("[DEBUG] 保存ファイル: " + filename);
    
    // スクリーンショットを撮影 - MT4ではWindowScreenShotを使用
    if(!WindowScreenShot(filename, 1024, 768))
    {
        Print("[ERROR] スクリーンショット撮影エラー: ", GetLastError());
        return;
    }
    
    if(DebugMode) Print("[DEBUG] スクリーンショット保存成功");
    
    // メッセージテキストを確認(空の場合はデフォルト値を使用)
    string messageToSend = MessageText;
    if(messageToSend == "" || messageToSend == NULL)
    {
        messageToSend = "MT4からの画像です(自動メッセージ)";
        Print("警告: MessageTextが空のため、デフォルトメッセージを使用します");
    }
    
    if(DebugMode) Print("[DEBUG] 送信メッセージ: " + messageToSend);
    
    // Discordに送信
    SendDiscordNotification(messageToSend, DiscordWebhookURL, filename);
}

//+------------------------------------------------------------------+
//| Discordに通知を送る関数
//| @param message 送信するメッセージ
//| @param webhook DiscordのWebhook URL
//| @param fileName 添付するファイル名(省略可能引数)
//+------------------------------------------------------------------+
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=\"file\"; 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);
    }

    // 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リクエストを実行し、データを取得                     |
//+------------------------------------------------------------------+
int WebRequest2(const string method, const string url, const string headers, int timeout, uchar &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;
        }

        uchar 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", "", NULL, 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;
        }

        uchar 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);
}

Discussion