😽
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