😽

MQL5Discord

に公開

MQL5成功

//+------------------------------------------------------------------+
//|                                    Discord Notification EA.mq5    |
//|                                          Copyright 2025          |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025"
#property link      ""
#property version   "1.00"
#property strict

//+------------------------------------------------------------------+
//|                                                      WinINet.mqh |
//|                                     Copyright 2023-2024, Geraked |
//|                                       https://github.com/geraked |
//+------------------------------------------------------------------+
#define WININET_TIMEOUT_SECS   300
#define WININET_BUFF_SIZE      16384
#define WININET_KERNEL_ERRORS  true

#define INTERNET_FLAG_PRAGMA_NOCACHE            0x00000100
#define INTERNET_FLAG_KEEP_CONNECTION           0x00400000
#define INTERNET_FLAG_SECURE                    0x00800000
#define INTERNET_FLAG_RELOAD                    0x80000000
#define INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP   0x00008000
#define INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS  0x00004000
#define INTERNET_FLAG_IGNORE_CERT_DATE_INVALID  0x00002000
#define INTERNET_FLAG_IGNORE_CERT_CN_INVALID    0x00001000
#define INTERNET_FLAG_NO_AUTO_REDIRECT          0x00200000

#define INTERNET_OPTION_HTTP_DECODING           65
#define INTERNET_OPTION_SEND_TIMEOUT            5
#define INTERNET_OPTION_RECEIVE_TIMEOUT         6
#define HTTP_QUERY_CONTENT_LENGTH               5
#define HTTP_QUERY_STATUS_CODE                  19
#define HTTP_QUERY_STATUS_TEXT                  20
#define HTTP_QUERY_RAW_HEADERS                  21
#define HTTP_QUERY_RAW_HEADERS_CRLF             22

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

#import "wininet.dll"
long InternetOpenW(const ushort &lpszAgent[], int dwAccessType, const ushort &lpszProxyName[], const ushort &lpszProxyBypass[], uint dwFlags);
long InternetConnectW(long hInternet, const ushort &lpszServerName[], int nServerPort, const ushort &lpszUsername[], const ushort &lpszPassword[], int dwService, uint dwFlags, int dwContext);
long HttpOpenRequestW(long hConnect, const ushort &lpszVerb[], const ushort &lpszObjectName[], const ushort &lpszVersion[], const ushort &lpszReferer[], const ushort &lplpszAcceptTypes[][], uint dwFlags, int dwContext);
int InternetCloseHandle(long hInternet);
int InternetSetOptionW(long hInternet, int dwOption, long &lpBuffer, int dwBufferLength);
int HttpAddRequestHeadersW(long hRequest, const ushort &lpszHeaders[], int dwHeadersLength, uint dwModifiers);
int HttpSendRequestW(long hRequest, const ushort &lpszHeaders[], int dwHeadersLength, const uchar &lpOptional[], int dwOptionalLength);
int HttpSendRequestExW(long hRequest, long lpBuffersIn, long lpBuffersOut, uint dwFlags, int dwContext);
int HttpEndRequestW(long hRequest, long lpBuffersOut, uint dwFlags, int dwContext);
int HttpQueryInfoW(long hRequest, int dwInfoLevel, uchar &lpvBuffer[], int &lpdwBufferLength, int &lpdwIndex);
int InternetWriteFile(long hFile, const uchar &lpBuffer[], int dwNumberOfBytesToWrite, int &lpdwNumberOfBytesWritten);
int InternetReadFile(long hFile, uchar &lpBuffer[], int dwNumberOfBytesToRead, int &lpdwNumberOfBytesRead);
#import

struct WininetRequest {
    string           method;
    string           host;
    int              port;
    string           path;
    string           headers;
    uchar            data[];
    string           data_str;
    void             WininetRequest() {
        method = "GET";
        port = 443;
        path = "/";
        headers = "";
    }
};

struct WininetResponse {
    int              status;
    string           headers;
    uchar            data[];
    string           GetDataStr() {
        return UnicodeUnescape(data);
    }
};

//+------------------------------------------------------------------+
//| HTTP request using wininet.dll                                   |
//+------------------------------------------------------------------+
int WebReq(
    const string  method,           // HTTP method
    const string  host,             // host name
    const string  path,             // URL path
    int           port,             // port number
    bool          secure,           // use HTTPS
    const string  headers,          // HTTP request headers
    const uchar   &data[],          // HTTP request body
    uchar         &result[],        // server response data
    string        &result_headers   // headers of server response
) {

//- Declare the variables.
    ushort buff[WININET_BUFF_SIZE / 2], buff2[WININET_BUFF_SIZE / 2];
    uchar cbuff[WININET_BUFF_SIZE];
    int n, bLen, bLen2, bIdx;
    long lval;
    long session, connection, request;
    int status;
    uint flags;
    string head;

//- Create the NULL string.
    ushort nill[2] = {0, 0};
    ushort nill2[2][2] = {{0, 0}, {0, 0}};

//- Create a session.
    StringToShortArray(GetUserAgent(), buff);
    session = InternetOpenW(buff, 0, nill, nill, 0);
    if (session <= 0)
        return _wininetErr("InternetOpen");

//- Enable automatically decoding from gzip and deflate.
    lval = 1;
    if (!InternetSetOptionW(session, INTERNET_OPTION_HTTP_DECODING, lval, sizeof(int)))
        return _wininetErr("InternetSetOption, DECODING", session);

//- Set timeouts.
    lval = WININET_TIMEOUT_SECS * 1000;
    if (!InternetSetOptionW(session, INTERNET_OPTION_SEND_TIMEOUT, lval, sizeof(int)))
        return _wininetErr("InternetSetOption, SEND_TIMEOUT", session);
    lval = WININET_TIMEOUT_SECS * 1000;
    if (!InternetSetOptionW(session, INTERNET_OPTION_RECEIVE_TIMEOUT, lval, sizeof(int)))
        return _wininetErr("InternetSetOption, RECEIVE_TIMEOUT", session);

//- Create a connection.
    StringToShortArray(host, buff);
    connection = InternetConnectW(session, buff, port, nill, nill, 3, 0, 0);
    if (connection <= 0)
        return _wininetErr("InternetConnect", session);

//- Open a request.
    StringToShortArray(method, buff);
    StringToShortArray(path, buff2);
    flags = INTERNET_FLAG_RELOAD | INTERNET_FLAG_PRAGMA_NOCACHE;
    flags |= INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;
    if (port == 443 || (secure && port != 80)) flags |= INTERNET_FLAG_SECURE;
    if (method == "GET") flags |= INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS;
    if (method != "GET") flags |= INTERNET_FLAG_NO_AUTO_REDIRECT;
    request = HttpOpenRequestW(connection, buff, buff2, nill, nill, nill2, flags, 0);
    if (request <= 0)
        return _wininetErr("HttpOpenRequest", session, connection);

//- Add request headers.
    n = ArraySize(data);
    if (n > 0 && data[n - 1] == 0) n--;
    head = StringFormat("Accept-Encoding: gzip, deflate\r\n"
                        "Content-Length: %d\r\n", n);
    bLen = StringToShortArray(head + headers, buff);
    if (bLen > 0 && buff[bLen - 1] == 0) bLen--;
    if (!HttpAddRequestHeadersW(request, buff, bLen, 0x80000000))
        return _wininetErr("HttpAddRequestHeaders", session, connection, request);

//- Send the request.
    int cnt = 1;
    while (!HttpSendRequestExW(request, 0, 0, 0, 0)) {
        if (cnt == 5)
            return _wininetErr("HttpSendRequestEx", session, connection, request);
        Sleep(1000);
        cnt++;
    }
    bIdx = 0;
    bLen2 = 0;
    while (true) {
        bLen = MathMin(WININET_BUFF_SIZE, n - bIdx);
        if (bLen <= 0) break;
        ArrayCopy(cbuff, data, 0, bIdx, bLen);
        if (!InternetWriteFile(request, cbuff, bLen, bLen2))
            return _wininetErr("InternetWriteFile", session, connection, request);
        bIdx += bLen2;
    }
    if (!HttpEndRequestW(request, 0, 0, 0))
        return _wininetErr("HttpEndRequest", session, connection, request);

//- Fetch the status code from the response header.
    bLen = WININET_BUFF_SIZE;
    bIdx = 0;
    if (!HttpQueryInfoW(request, HTTP_QUERY_STATUS_CODE, cbuff, bLen, bIdx))
        return _wininetErr("HttpQueryInfo, STATUS_CODE", session, connection, request);
    status = (int) UnicodeUnescape(cbuff, bLen);

//- Fetch the entire response header.
    bLen = WININET_BUFF_SIZE;
    bIdx = 0;
    if (!HttpQueryInfoW(request, HTTP_QUERY_RAW_HEADERS_CRLF, cbuff, bLen, bIdx))
        return _wininetErr("HttpQueryInfo, HEADER", session, connection, request);
    result_headers = UnicodeUnescape(cbuff, bLen);

//- Fetch the response body.
    bLen = 0;
    while (true) {
        if (!InternetReadFile(request, cbuff, WININET_BUFF_SIZE, bLen))
            return _wininetErr("InternetReadFile", session, connection, request);
        if (bLen <= 0) break;
        ArrayCopy(result, cbuff, ArraySize(result), 0, bLen);
    }

//- Close the request, connection, and session.
    if (!InternetCloseHandle(request))
        _wininetErr("InternetCloseRequest");
    if (!InternetCloseHandle(connection))
        _wininetErr("InternetCloseConnection");
    if (!InternetCloseHandle(session))
        _wininetErr("InternetCloseSession");

    return status;
}

//+------------------------------------------------------------------+
//| Overload                                                         |
//+------------------------------------------------------------------+
bool WebReq(WininetRequest &req, WininetResponse &res) {
    if (req.method == NULL || req.method == "") req.method = "GET";
    if (req.path == NULL || req.path == "") req.path = "/";
    if (req.headers == NULL) req.headers = "";
    if (req.port == 0) req.port = 443;
    if (ArraySize(req.data) > 0 || req.data_str == NULL)
        res.status = WebReq(req.method, req.host, req.path, req.port, false, req.headers, req.data, res.data, res.headers);
    else {
        uchar data_arr[];
        StringToCharArray(req.data_str, data_arr, 0, WHOLE_ARRAY, CP_UTF8);
        res.status = WebReq(req.method, req.host, req.path, req.port, false, req.headers, data_arr, res.data, res.headers);
    }
    if (res.status == -1) return false;
    return true;
}

//+------------------------------------------------------------------+
//| Generate User-Agent for the HTTP request header.                 |
//+------------------------------------------------------------------+
string GetUserAgent() {
    return StringFormat(
               "%s/%d (%s; %s; %s %d Cores; %dMB RAM) WinINet/1.6",
               TerminalInfoString(TERMINAL_NAME),
               TerminalInfoInteger(TERMINAL_BUILD),
               TerminalInfoString(TERMINAL_OS_VERSION),
               TerminalInfoInteger(TERMINAL_X64) ? "x64" : "x32",
               TerminalInfoString(TERMINAL_CPU_NAME),
               TerminalInfoInteger(TERMINAL_CPU_CORES),
               TerminalInfoInteger(TERMINAL_MEMORY_PHYSICAL)
           );
}

//+------------------------------------------------------------------+
//| Remove 0 characters from the fixed size array.                   |
//+------------------------------------------------------------------+
template<typename T>
int ArrayRemoveGaps(T &arr[], int n) {
    int i, j, k;
    T c;
    k = 0;
    for (i = 0; i < n; i++) {
        if (arr[i] != 0) {
            k++;
            continue;
        }
        c = 0;
        for (j = i + 1; j < n; j++) {
            if (arr[j] != 0) {
                c = arr[j];
                arr[j] = 0;
                break;
            }
        }
        if (c == 0) break;
        arr[i] = c;
        k++;
    }
    if (k < n)
        arr[k] = 0;
    return k;
}

//+------------------------------------------------------------------+
//| Unescape Unicode characters from the string.                     |
//+------------------------------------------------------------------+
string UnicodeUnescape(string str) {
    ushort s[];
    ushort c1, c2, c, x;
    int n, i, j, k;
    n = StringLen(str);
    ArrayResize(s, n);
    i = 0;
    j = 0;
    while (i < n) {
        c1 = str[i];
        c2 = i + 1 < n ? str[i + 1] : 0;
        if (c1 == '\\' && (c2 == 'u' || c2 == 'U')) {
            if (i + 5 < n) {
                c = 0;
                for (k = i + 2; k < i + 6; k++) {
                    x = str[k];
                    if (x >= '0' && x <= '9') x = x - '0';
                    else if (x >= 'a' && x <= 'f') x = x - 'a' + 10;
                    else if (x >= 'A' && x <= 'F') x = x - 'A' + 10;
                    else break;
                    c = (c << 4) | (x & 0xF);
                }
                if (k == i + 6) {
                    if (c != 0) s[j++] = c;
                    i += 6;
                } else {
                    s[j++] = c1;
                    i++;
                }
            } else {
                s[j++] = c1;
                i++;
            }
        } else {
            s[j++] = c1;
            i++;
        }
    }
    return ShortArrayToString(s, 0, j);
}

//+------------------------------------------------------------------+
//| Overload                                                         |
//+------------------------------------------------------------------+
string UnicodeUnescape(ushort &arr[], int n = 0) {
    if (n == 0) n = ArraySize(arr);
    n = ArrayRemoveGaps(arr, n);
    return UnicodeUnescape(ShortArrayToString(arr, 0, n));
}

//+------------------------------------------------------------------+
//| Overload                                                         |
//+------------------------------------------------------------------+
string UnicodeUnescape(uchar &arr[], int n = 0) {
    if (n == 0) n = ArraySize(arr);
    n = ArrayRemoveGaps(arr, n);
    return UnicodeUnescape(CharArrayToString(arr, 0, n, CP_UTF8));
}

//+------------------------------------------------------------------+
//| Handle WinINet errors.                                           |
//+------------------------------------------------------------------+
int _wininetErr(string title = "", long session = 0, long connection = 0, long request = 0) {
    uint err = kernel32::GetLastError();
    if (WININET_KERNEL_ERRORS)
        PrintFormat("Error (%s, %s): #%d", "WinINet", title, err);
    if (request > 0) InternetCloseHandle(request);
    if (connection > 0) InternetCloseHandle(connection);
    if (session > 0) InternetCloseHandle(session);
    return -1;
}

//+------------------------------------------------------------------+
//| 日本語/Unicode文字に対応した強化JSON文字列エスケープ関数          |
//+------------------------------------------------------------------+
string EnhancedEscapeJSON(string text)
{
    // 空の文字列チェック
    if(text == "" || text == NULL)
    {
        Print("警告: EnhancedEscapeJSONに空の文字列が渡されました");
        return "MT5からの自動メッセージ";  // 空文字列を返さずデフォルト値を返す
    }
    
    Print("EnhancedEscapeJSON入力: ", text);
    
    string escaped = text;
    
    // JSONのための特殊文字をエスケープ
    StringReplace(escaped, "\\", "\\\\");   // バックスラッシュは最初に処理する必要あり
    StringReplace(escaped, "\"", "\\\"");   // ダブルクォート
    StringReplace(escaped, "\n", "\\n");    // 改行
    StringReplace(escaped, "\r", "\\r");    // キャリッジリターン
    StringReplace(escaped, "\t", "\\t");    // タブ
    // バックスペースとフォームフィードはMQL5では非対応
    
    Print("特殊文字エスケープ後: ", escaped);
    
    return escaped;  // 文字変換処理を単純化して問題箇所を減らす
}

//+------------------------------------------------------------------+
//| URLからホスト名とパス名を抽出する関数                             |
//+------------------------------------------------------------------+
bool ParseURL(string url, string &host, string &path, int &port)
{
    host = "";
    path = "";
    port = 80;
    
    int pos = StringFind(url, "://");
    if(pos < 0) return(false);
    
    string protocol = StringSubstr(url, 0, pos);
    if(StringCompare(protocol, "https", false) == 0) port = 443;
    
    string temp = StringSubstr(url, pos + 3);
    pos = StringFind(temp, "/");
    
    if(pos < 0)
    {
        host = temp;
        path = "/";
    }
    else
    {
        host = StringSubstr(temp, 0, pos);
        path = StringSubstr(temp, pos);
    }
    
    // ポート番号が含まれている場合
    int portPos = StringFind(host, ":");
    if(portPos >= 0)
    {
        string portStr = StringSubstr(host, portPos + 1);
        host = StringSubstr(host, 0, portPos);
        port = (int)StringToInteger(portStr);
    }
    
    return(true);
}

//+------------------------------------------------------------------+
//| マルチパートフォームデータの境界文字列を生成する関数              |
//+------------------------------------------------------------------+
string GenerateBoundary()
{
    return "----WebKitFormBoundary" + IntegerToString(GetTickCount());
}

//+------------------------------------------------------------------+
//| テキストメッセージのみをDiscordに送信する関数                     |
//+------------------------------------------------------------------+
bool SendDiscordTextNotification(string message, string webhookUrl)
{
    // URLからホスト名とパス名を抽出
    string host, path;
    int port;
    if(!ParseURL(webhookUrl, host, path, port))
    {
        Print("URLの解析に失敗しました");
        return false;
    }
    
    // JSONペイロードを作成
    string escapedMessage = EnhancedEscapeJSON(message);
    string payloadJson = "{\"content\":\"" + escapedMessage + "\"}";
    Print("テキスト送信用JSON: ", payloadJson);
    
    // WinINet.mqhライブラリを使用してHTTPリクエスト送信
    WininetRequest req;
    WininetResponse res;
    
    req.method = "POST";
    req.host = host;
    req.port = port;
    req.path = path;
    
    // ヘッダーを設定
    req.headers = "Content-Type: application/json\r\n";
    req.headers += "User-Agent: MT5/Discord-Integration\r\n";
    
    // UTF-8エンコーディングを使用するために、data_str経由でJSONを送信
    req.data_str = payloadJson;
    
    // リクエスト送信
    Print("テキストメッセージを送信中...");
    bool result = WebReq(req, res);
    
    if(!result)
    {
        Print("WebReq関数の実行に失敗しました");
        return false;
    }
    
    // レスポンスをチェック
    string response = res.GetDataStr();
    Print("Discord応答: ", response);
    Print("ステータスコード: ", res.status);
    
    // 成功の判定
    if((res.status >= 200 && res.status < 300) || StringFind(response, "\"id\"") >= 0)
    {
        Print("Discordへのテキスト送信成功");
        return true;
    }
    
    Print("Discordへのテキスト送信失敗");
    return false;
}

//+------------------------------------------------------------------+
//| 画像のみをDiscordに送信する関数                                   |
//+------------------------------------------------------------------+
bool SendDiscordImageNotification(string filename, string webhookUrl)
{
    // ファイルが存在するか確認
    if(!FileIsExist(filename))
    {
        Print("ファイルが存在しません: ", filename);
        return false;
    }
    
    // ファイルを開く - 共有モードを使用
    int filehandle = FileOpen(filename, FILE_READ|FILE_BIN|FILE_SHARE_READ);
    if(filehandle == INVALID_HANDLE)
    {
        Print("ファイルを開けませんでした: ", GetLastError());
        return false;
    }
    
    // ファイルサイズを取得
    ulong filesize = FileSize(filehandle);
    if(filesize == 0)
    {
        FileClose(filehandle);
        Print("ファイルサイズが0です");
        return false;
    }
    
    // ファイルの内容を読み込む
    uchar filedata[];
    ArrayResize(filedata, (int)filesize);
    FileReadArray(filehandle, filedata, 0, (int)filesize);
    FileClose(filehandle);
    
    // URLからホスト名とパス名を抽出
    string host, path;
    int port;
    if(!ParseURL(webhookUrl, host, path, port))
    {
        Print("URLの解析に失敗しました");
        return false;
    }
    
    // 境界文字列を生成
    string boundary = GenerateBoundary();
    
    // 空のJSONペイロード(メッセージなし)
    string payloadJson = "{\"content\":\"\"}";
    
    // マルチパートフォームデータを構築
    string formData = "";
    
    // パート1: JSON ペイロード
    formData += "--" + boundary + "\r\n";
    formData += "Content-Disposition: form-data; name=\"payload_json\"\r\n";
    formData += "Content-Type: application/json\r\n\r\n";
    formData += payloadJson + "\r\n";
    
    // パート2: ファイル
    formData += "--" + boundary + "\r\n";
    formData += "Content-Disposition: form-data; name=\"file\"; filename=\"" + filename + "\"\r\n";
    formData += "Content-Type: image/png\r\n\r\n";
    
    // 終了境界
    string endBoundary = "\r\n--" + boundary + "--\r\n";
    
    // マルチパートデータを直接コンストラクト
    uchar requestData[];
    
    // ファイルサイズを考慮して十分な配列サイズを確保
    int totalSize = StringLen(formData) * 3 + (int)filesize + StringLen(endBoundary) * 3;
    ArrayResize(requestData, totalSize);
    
    int pos = 0;
    
    // ヘッダー部分
    uchar temp[];
    StringToCharArray(formData, temp, 0, StringLen(formData), CP_UTF8);
    for(int i = 0; i < ArraySize(temp); i++)
        requestData[pos++] = temp[i];
    
    // ファイルデータ
    for(int i = 0; i < (int)filesize; i++)
        requestData[pos++] = filedata[i];
    
    // フッター部分
    ArrayFree(temp);
    StringToCharArray(endBoundary, temp, 0, StringLen(endBoundary), CP_UTF8);
    for(int i = 0; i < ArraySize(temp); i++)
        requestData[pos++] = temp[i];
    
    // 実際のデータサイズに配列を再調整
    ArrayResize(requestData, pos);
    
    Print("画像データを送信中... サイズ: ", pos, " バイト");
    
    // WinINet.mqhライブラリを使用してHTTPリクエスト送信
    WininetRequest req;
    WininetResponse res;
    
    req.method = "POST";
    req.host = host;
    req.port = port;
    req.path = path;
    
    // ヘッダーを設定
    req.headers = "Content-Type: multipart/form-data; boundary=" + boundary + "; charset=utf-8\r\n";
    req.headers += "User-Agent: MT5/Discord-Integration\r\n";
    req.headers += "Connection: close\r\n";
    
    // バイナリデータをセット
    ArrayResize(req.data, ArraySize(requestData));
    ArrayCopy(req.data, requestData, 0, 0, ArraySize(requestData));
    
    // リクエスト送信
    bool result = WebReq(req, res);
    
    if(!result)
    {
        Print("WebReq関数の実行に失敗しました");
        return false;
    }
    
    // レスポンスをチェック
    string response = res.GetDataStr();
    Print("Discord応答: ", response);
    Print("ステータスコード: ", res.status);
    
    // 成功の判定
    if((res.status >= 200 && res.status < 300) || StringFind(response, "\"id\"") >= 0)
    {
        Print("Discordへの画像送信成功");
        return true;
    }
    
    Print("Discordへの画像送信失敗");
    return false;
}

//+------------------------------------------------------------------+
//| テキストと画像の両方をDiscordに送信する関数                       |
//+------------------------------------------------------------------+
bool SendImageToDiscord(string filename, string webhookUrl, string message)
{
    // まずテキストメッセージを送信
    bool textSuccess = SendDiscordTextNotification(message, webhookUrl);
    
    // 次に画像を送信
    bool imageSuccess = SendDiscordImageNotification(filename, webhookUrl);
    
    // 両方成功した場合にtrueを返す
    return textSuccess && imageSuccess;
}

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

// グローバル変数
int lastScreenshotTime = 0;
string terminalDataPath = "";

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    // 初期化時に時間をリセット
    lastScreenshotTime = 0;
    
    // ターミナルデータパスを取得
    terminalDataPath = TerminalInfoString(TERMINAL_DATA_PATH);
    
    Print("Discord Image Sender EA 初期化完了");
    Print("データパス: ", terminalDataPath);
    Print("設定メッセージ: ", MessageText);
    
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    Print("Discord Image Sender EA 停止");
}

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

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

Discussion