Skip to content

Commit aa50ce4

Browse files
authored
merge PR #63 from 61-telegram-messenger into master
integration of Telegram messenger for logging and signaling
2 parents ce13c17 + bef95a9 commit aa50ce4

11 files changed

Lines changed: 173 additions & 50 deletions

File tree

.github/workflows/compile-mql.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
steps:
2929
# ------------------------------------------------------------------------------------------------------------------------------------
3030
- name: Checkout repository
31-
uses: actions/checkout@v4
31+
uses: actions/checkout@v5
3232
with:
3333
fetch-depth: 0 # fetch all branches and history (to access the source branch of PRs)
3434

config/global-config.example.ini

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,33 @@ Editor = drive:/path-to/editor.exe ; system editor to use for e
3131
; online loglevels (available values: debug, info, notice, warn, error, fatal, all, off)
3232
LogLevel = info ; general loglevel (built-in default: all)
3333
<ProgramName> = <loglevel> ; program-specific loglevel
34-
35-
; appender loglevels
34+
35+
; appender loglevels
3636
Log2Debug = all ; debugview output appender (built-in default: all)
3737
Log2Terminal = info ; terminal log appender (built-in default: all)
3838
Log2Alert = notice ; terminal alert appender (built-in default: notice)
3939
Log2File = all ; custom logfile appender (built-in default: off)
4040
Log2Mail = warn ; mail appender (built-in default: off)
4141
Log2SMS = error ; SMS appender (built-in default: off)
42-
43-
44-
[Tester.Log]
45-
; tester loglevels
42+
Log2Telegram = warn ; Telegram appender (built-in default: off)
43+
44+
45+
[Tester.Log]
46+
; tester loglevels
4647
LogLevel = notice ; general loglevel (built-in default: off, for max performance)
4748
Config = info ; program-specific loglevel, e.g. for script "Config"
48-
49-
; appender loglevels
49+
50+
; appender loglevels
5051
Log2Debug = all ; debugview output appender (built-in default: all)
5152
Log2Terminal = notice ; terminal log appender (built-in default: notice)
5253
Log2Alert = warn ; terminal alert appender (built-in default: warn)
5354
Log2File = all ; custom logfile appender (built-in default: all)
5455
Log2Mail = off ; mail appender (built-in default: off)
5556
Log2SMS = off ; SMS appender (built-in default: off)
56-
57-
58-
[Mail]
57+
Log2Telegram = error ; Telegram appender (built-in default: off)
58+
59+
60+
[Mail]
5961
Sendmail = /bin/email ; MTA configuration in "/etc/email/email.conf"
6062
Sender = <email-address>
6163
Receiver = <email-address>
@@ -65,13 +67,25 @@ Receiver = <email-address>
6567
Provider = Clickatell
6668
Receiver = <phone-number>
6769

68-
6970
[SMS.Clickatell]
7071
Username = <username>
7172
Password = <password>
7273
api_id = <api-id>
7374

7475

76+
[Telegram]
77+
LogChannel = <channel1>
78+
SignalChannel = <channel2>
79+
80+
[Telegram channel1]
81+
ChannelId = <channel1-id>
82+
Token = <bot-token>
83+
84+
[Telegram channel2]
85+
ChannelId = <channel2-id>
86+
Token = <bot-token>
87+
88+
7589
[AccountCompanies]
7690
; Format:
7791
; <server-name>|<company-name>|<company-id> = <company-alias>

mql40/include/rsf/MT4Expander.mqh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@
127127
bool StrIsNull(string str);
128128
bool StrStartsWith(string str, string prefix);
129129
string StringToStr(string str);
130+
string AnsiToUtf8(string str);
131+
string Utf8ToAnsi(string str);
130132

131133
// conversion functions
132134
string BarModelDescription(int id);

mql40/include/rsf/api.mqh

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,7 @@ string JoinInts(int &values[], string separator=", ");;
247247
bool ManageDoubleIndicatorBuffer(int id, double buffer[]);;
248248
bool ObjectCreateRegister(string name, int type, int window=0, datetime time1=NULL, double price1=NULL, datetime time2=NULL, double price2=NULL, datetime time3=NULL, double price3=NULL);;
249249
bool ParseDateTime(string value, int flags, int &result[]);;
250-
bool UpdateTrend(double &values[], int offset, double &trend[], double &uptrend[], double &downtrend[], double &uptrend2[], bool enableColoring=false, bool enableUptrend2=false, int lineStyle=EMPTY, int normalizeDigits=EMPTY_VALUE);;
251-
250+
bool UpdateTrend(double values[], int offset, double &trend[], double &uptrend[], double &downtrend[], double &uptrend2[], bool enableColoring=false, bool enableUptrend2=false, int lineStyle=EMPTY, int digits=EMPTY_VALUE);;
252251

253252
// include/rsf/functions/chartlegend.mqh
254253
string CreateChartLegend();;
@@ -257,7 +256,6 @@ bool RemoveChartLegend();;
257256
void UpdateBandLegend(string legendName, string indicatorName, string status, color bandsColor, double upperValue, double lowerValue);;
258257
void UpdateTrendLegend(string legendName, string indicatorName, string status, color uptrendColor, color downtrendColor, double value, int trend=0);;
259258

260-
261259
// include/rsf/functions/configuration.mqh
262260
string GetAccountConfigPath(string company="", int account=NULL);;
263261

@@ -301,7 +299,6 @@ double GetIniDouble(string fileName, string section, string key, double defaul
301299

302300
bool WriteIniString(string fileName, string section, string key, string value);;
303301

304-
305302
// include/rsf/functions/log.mqh
306303
int catch(string caller, int error=NO_ERROR, bool popOrder=false);;
307304
int debug(string message, int error=NO_ERROR, int loglevel=NULL);;
@@ -327,17 +324,16 @@ int log2Debug (string message, int error, int level);;
327324
int log2File (string message, int error, int level);;
328325
int log2Mail (string message, int error, int level);;
329326
int log2SMS (string message, int error, int level);;
327+
int log2Telegram(string message, int error, int level);;
330328
int log2Terminal(string message, int error, int level);;
331329

332330
bool SetLogfile(string filename);;
333331

334-
335332
// include/rsf/functions/scriptrunner.mqh
336333
bool RunScript(string name, string parameters = "");;
337334
bool ScriptRunner.GetParameters(string &parameters[]);;
338335
bool ScriptRunner.SetParameters(string parameters);;
339336

340-
341337
// include/rsf/functions/iCustom/
342338
double icALMA(int timeframe, int maPeriods, string maAppliedPrice, double distributionOffset, double distributionSigma, double maReversalFilter, int iBuffer, int iBar);;
343339
double icHalfTrend(int timeframe, int periods, int iBuffer, int iBar);;
@@ -347,7 +343,6 @@ double icMaChannel(int timeframe, string channelDefinition, int iBuffer, int i
347343
double icMovingAverage(int timeframe, string maMethod, int maPeriods, string maAppliedPrice, int iBuffer, int iBar);;
348344
double icZigZag(int timeframe, int periods, int iBuffer, int iBar);;
349345

350-
351346
// include/rsf/functions/shared/
352347
int Abs(int value);;
353348
string LoglevelDescription(int level);; // also implemented in MT4Expander
@@ -360,15 +355,13 @@ string StrPadRight(string str, int padLength, string padString=" ");;
360355
string StrSubstr(string str, int start, int length=INT_MAX);;
361356
string StrTrim(string str);;
362357

363-
364358
// include/rsf/functions/ta/
365359
bool ALMA.CalculateWeights(int periods, double offset, double sigma, double &weights[]);;
366360
double ATR(string symbol, int timeframe, int periods, int offset);;
367361
double iADR(int flags=NULL);;
368362
double JMASeries(int h, int iMaxBar, int iStartbar, int length, int phase, double series, int bar);;
369363
bool NLMA.CalculateWeights(int cycles, int cyclePeriods, double &weights[]);;
370364

371-
372365
// include/rsf/structs/Bar.mqh
373366
datetime bar.Time (double bar[]);;
374367
double bar.Open (double bar[]);;
@@ -400,7 +393,6 @@ int bars.setVolume(double &bar[][], int i, int volume);;
400393

401394
string BAR.toStr (double bar[]);;
402395

403-
404396
// include/rsf/structs/OrderExecution.mqh
405397
int oe.Error (/*ORDER_EXECUTION*/int oe[]);;
406398
bool oe.IsError (/*ORDER_EXECUTION*/int oe[]);;
@@ -516,11 +508,9 @@ double oes.setRemainingLots (/*ORDER_EXECUTION*/int &oe[][], int i, double
516508

517509
string ORDER_EXECUTION.toStr (/*ORDER_EXECUTION*/int oe[]);;
518510

519-
520511
// include/rsf/structs/mt4/
521512
// include/rsf/structs/win32/
522513

523-
524514
// libraries/rsfHistory1.ex4
525515
int HistoryFile1.Open (string symbol, int timeframe, string description, int digits, int format, int mode, string directory="");;
526516
bool HistoryFile1.Close (int hFile);;
@@ -537,7 +527,6 @@ int HistorySet1.Get (string symbol, string directory="");;
537527
bool HistorySet1.Close (int hSet);;
538528
bool HistorySet1.AddTick(int hSet, datetime time, double value, int flags=NULL);;
539529

540-
541530
// libraries/rsfHistory2.ex4
542531
int HistoryFile2.Open (string symbol, int timeframe, string description, int digits, int format, int mode, string directory="");;
543532
bool HistoryFile2.Close (int hFile);;
@@ -554,7 +543,6 @@ int HistorySet2.Get (string symbol, string directory="");;
554543
bool HistorySet2.Close (int hSet);;
555544
bool HistorySet2.AddTick(int hSet, datetime time, double value, int flags=NULL);;
556545

557-
558546
// libraries/rsfHistory3.ex4
559547
int HistoryFile3.Open (string symbol, int timeframe, string description, int digits, int format, int mode, string directory="");;
560548
bool HistoryFile3.Close (int hFile);;
@@ -571,7 +559,6 @@ int HistorySet3.Get (string symbol, string directory="");;
571559
bool HistorySet3.Close (int hSet);;
572560
bool HistorySet3.AddTick(int hSet, datetime time, double value, int flags=NULL);;
573561

574-
575562
// libraries/rsfStdlib.ex4
576563
int AddSymbolGroup(int sgs[], string name, string description, color bgColor);;
577564
bool AquireLock(string mutex);;
@@ -706,6 +693,7 @@ int SearchDoubleArray(double &haystack[], double needle);;
706693
int SearchIntArray(int &haystack[], int needle);;
707694
int SearchStringArray(string &haystack[], string needle);;
708695
int SearchStringArrayI(string &haystack[], string needle);;
696+
bool SendTelegramMessage(string channel, string message);;
709697
bool SetRawSymbolTemplate(int symbol[], int type);;
710698
bool SortOpenTickets(int &keys[][]);;
711699
bool SortStrings(string &values[]);;
@@ -724,8 +712,8 @@ string WaitForSingleObjectValueToStr(int value);;
724712
int WinExecWait(string cmdLine, int cmdShow);;
725713
string WordToHexStr(int word);;
726714

727-
728715
// libraries/rsfMT4Expander.dll
716+
string AnsiToUtf8(string str);;
729717
bool AppendLogMessageA(int ec[], datetime time, string message, int error, int level);;
730718
string BarModelDescription(int id);;
731719
string BarModelToStr(int id);;
@@ -747,6 +735,7 @@ int ec_SetLoglevelDebug (int &ec[], int level);;
747735
int ec_SetLoglevelFile (int &ec[], int level);;
748736
int ec_SetLoglevelMail (int &ec[], int level);;
749737
int ec_SetLoglevelSMS (int &ec[], int level);;
738+
int ec_SetLoglevelTelegram (int &ec[], int level);;
750739
int ec_SetLoglevelTerminal (int &ec[], int level);;
751740
int ec_SetMqlError (int &ec[], int error);;
752741
int ec_SetProgramCoreFunction(int &ec[], int id);;
@@ -757,6 +746,7 @@ int ec_SuperLoglevelDebug (int pid);;
757746
int ec_SuperLoglevelFile (int pid);;
758747
int ec_SuperLoglevelMail (int pid);;
759748
int ec_SuperLoglevelSMS (int pid);;
749+
int ec_SuperLoglevelTelegram (int pid);;
760750
int ec_SuperLoglevelTerminal (int pid);;
761751
string ec_SuperProgramName (int pid);;
762752
bool EmptyIniSectionA(string fileName, string section);;
@@ -911,8 +901,8 @@ string TimeframeToStr(int timeframe);;
911901
string TradeDirectionDescription(int direction);;
912902
string TradeDirectionToStr(int direction);;
913903
string UninitReasonToStr(int reason);;
904+
string Utf8ToAnsi(string str);;
914905
int WM_MT4();;
915906

916-
917907
// libraries/rsfMT4Expander.dll: program-specific api
918908
int Grid_GetChartHeight(int hChart, int lastHeight);;

mql40/include/rsf/functions/log.mqh

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* | log2File() | LogfileAppender | configurable |
3030
* | log2Mail() | MailAppender | configurable |
3131
* | log2SMS() | SMSAppender | configurable |
32+
* | log2Telegram() | TelegramAppender | configurable |
3233
* +----------------+--------------------------------------------------------------+------------------+
3334
* | SetLogfile() | set a logfile for the LogfileAppender | per MQL program |
3435
* +----------------+--------------------------------------------------------------+------------------+
@@ -240,6 +241,7 @@ int log(string message, int error, int level) {
240241
if (__ExecutionContext[EC.loglevelAlert ] != LOG_OFF) log2Alert (message, error, level); // after fast appenders as it may lock the UI thread in tester
241242
if (__ExecutionContext[EC.loglevelMail ] != LOG_OFF) log2Mail (message, error, level); // slow appenders last (launches a new process)
242243
if (__ExecutionContext[EC.loglevelSMS ] != LOG_OFF) log2SMS (message, error, level); // ...
244+
if (__ExecutionContext[EC.loglevelTelegram] != LOG_OFF) log2Telegram(message, error, level); // ...
243245
}
244246
else if (level >= LOG_FATAL) {
245247
if (__ExecutionContext[EC.loglevelTerminal] != LOG_OFF) log2Terminal(message, error, level); // built-in log appenders always process LOG_FATAL
@@ -550,7 +552,7 @@ int log2SMS(string message, int error, int level) {
550552
ec_SetLoglevelSMS(__ExecutionContext, LOG_OFF); // prevent recursive calls
551553

552554
string text = LoglevelDescription(level) +": "+ Symbol() +","+ PeriodDescription() +" "+ ModuleName(true) +"::"+ message + ifString(error, " ["+ ErrorToStr(error) +"]", "") + NL
553-
+"("+ TimeToStr(TimeLocalEx("log2SMS(4)"), TIME_MINUTES|TIME_SECONDS) +", "+ GetAccountAlias() +")";
555+
+"("+ TimeToStr(TimeLocalEx("log2SMS(3)"), TIME_MINUTES|TIME_SECONDS) +", "+ GetAccountAlias() +")";
554556

555557
if (SendSMS("", text)) {
556558
ec_SetLoglevelSMS(__ExecutionContext, configLevel); // restore the configuration or leave it disabled
@@ -561,6 +563,59 @@ int log2SMS(string message, int error, int level) {
561563
}
562564

563565

566+
/**
567+
* Send a log message to the Telegram appender.
568+
*
569+
* @param string message - log message
570+
* @param int error - error linked to the message (if any)
571+
* @param int level - log level of the message
572+
*
573+
* @return int - the same error or the configured SMS loglevel if parameter level is LOG_OFF
574+
*/
575+
int log2Telegram(string message, int error, int level) {
576+
static string channel = "";
577+
578+
// read the configuration on first usage
579+
int configLevel = __ExecutionContext[EC.loglevelTelegram]; if (!configLevel) {
580+
int pid = __ExecutionContext[EC.pid];
581+
if (__isSuperContext) configLevel = ec_SuperLoglevelTelegram(pid); // an indicator loaded by iCustom()
582+
if (!configLevel) {
583+
string section = ifString(__isTesting, "Tester.", "") +"Log", key = "Log2Telegram";
584+
string sValue = GetConfigString(section, key, "off"); // built-in default: off
585+
configLevel = StrToLogLevel(sValue, F_ERR_INVALID_PARAMETER);
586+
if (!configLevel) configLevel = _int(LOG_OFF, catch("log2Telegram(1) invalid loglevel configuration ["+ section +"]->"+ key +" = \""+ sValue +"\"", ERR_INVALID_CONFIG_VALUE));
587+
}
588+
if (channel == "") {
589+
section = "Telegram";
590+
key = "LogChannel";
591+
channel = GetConfigString(section, key);
592+
if (channel == "") configLevel = _int(LOG_OFF, catch("log2Telegram(2) missing log appender configuration ["+ section +"]->"+ key, ERR_INVALID_CONFIG_VALUE));
593+
}
594+
ec_SetLoglevelTelegram(__ExecutionContext, configLevel);
595+
}
596+
if (level == LOG_OFF) return(configLevel);
597+
598+
// apply the configured loglevel filter
599+
if (level >= configLevel) {
600+
static bool isRecursion = false; if (isRecursion) {
601+
Alert("log2Telegram(3) recursion: ", message, ", error: ", error, ", ", LoglevelToStrA(level));
602+
return(error);
603+
}
604+
isRecursion = true;
605+
ec_SetLoglevelTelegram(__ExecutionContext, LOG_OFF); // prevent recursive calls
606+
607+
string text = LoglevelDescription(level) +": "+ Symbol() +","+ PeriodDescription() +" "+ ModuleName(true) +"::"+ message + ifString(error, " ["+ ErrorToStr(error) +"]", "") + NL
608+
+"("+ TimeToStr(TimeLocalEx("log2Telegram(4)"), TIME_MINUTES|TIME_SECONDS) +", "+ GetAccountAlias() +")";
609+
610+
if (SendTelegramMessage(channel, text)) {
611+
ec_SetLoglevelTelegram(__ExecutionContext, configLevel); // restore the configuration or leave it disabled
612+
}
613+
isRecursion = false;
614+
}
615+
return(error);
616+
}
617+
618+
564619
/**
565620
* Send a log message to the terminal's log system.
566621
*
@@ -655,6 +710,9 @@ bool SetLogfile(string filename) {
655710
}
656711

657712

713+
#import "rsfStdlib.ex4"
714+
bool SendTelegramMessage(string channel, string message);
715+
658716
#import "kernel32.dll"
659717
void OutputDebugStringA(string message);
660718

@@ -665,16 +723,18 @@ bool SetLogfile(string filename) {
665723
int ec_SuperLoglevelFile (int pid);
666724
int ec_SuperLoglevelMail (int pid);
667725
int ec_SuperLoglevelSMS (int pid);
726+
int ec_SuperLoglevelTelegram(int pid);
668727
int ec_SuperLoglevelTerminal(int pid);
669728
string ec_SuperProgramName (int pid);
670729

671-
int ec_SetLoglevel (int ec[], int level);
672-
int ec_SetLoglevelAlert (int ec[], int level);
673-
int ec_SetLoglevelDebug (int ec[], int level);
674-
int ec_SetLoglevelFile (int ec[], int level);
675-
int ec_SetLoglevelMail (int ec[], int level);
676-
int ec_SetLoglevelSMS (int ec[], int level);
677-
int ec_SetLoglevelTerminal (int ec[], int level);
730+
int ec_SetLoglevel (int ec[], int level);
731+
int ec_SetLoglevelAlert (int ec[], int level);
732+
int ec_SetLoglevelDebug (int ec[], int level);
733+
int ec_SetLoglevelFile (int ec[], int level);
734+
int ec_SetLoglevelMail (int ec[], int level);
735+
int ec_SetLoglevelSMS (int ec[], int level);
736+
int ec_SetLoglevelTelegram(int ec[], int level);
737+
int ec_SetLoglevelTerminal(int ec[], int level);
678738

679739
bool AppendLogMessageA(int ec[], datetime time, string message, int error, int level);
680740
bool SetLogfileA (int ec[], string file);

mql40/include/rsf/stdfunctions.mqh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6390,7 +6390,7 @@ bool SendEmail(string sender, string receiver, string subject, string message) {
63906390
int result = WinExec(cmdLine, SW_HIDE); // SW_SHOW | SW_HIDE
63916391
if (result < 32) return(!catch("SendEmail(11)->kernel32::WinExec(cmdLine=\""+ cmdLine +"\") "+ ShellExecuteErrorDescription(result), ERR_WIN32_ERROR+result));
63926392

6393-
if (IsLogDebug()) logDebug("SendEmail(12) Mail to "+ receiver +" transmitted: \""+ subject +"\"");
6393+
logInfo("SendEmail(12) mail to "+ receiver +" transmitted: \""+ subject +"\"");
63946394
return(!catch("SendEmail(13)"));
63956395
}
63966396

@@ -6463,7 +6463,7 @@ bool SendSMS(string receiver, string message) {
64636463
// Connecting to api.clickatell.com|196.216.236.7|:443... failed: Permission denied.
64646464
// Giving up.
64656465

6466-
if (IsLogDebug()) logDebug("SendSMS(10) SMS sent to "+ receiverBak +": \""+ message +"\"");
6466+
logInfo("SendSMS(10) SMS sent to "+ receiverBak +": \""+ message +"\"");
64676467
return(!catch("SendSMS(11)"));
64686468
}
64696469

mql40/include/rsf/stdlib.mqh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@
184184
int SortTicketsChronological(int &tickets[]);
185185
string GetWindowsShortcutTarget(string lnkFile);
186186
int WinExecWait(string cmdLine, int cmdShow);
187+
bool SendTelegramMessage(string channel, string message);
187188
#import
188189

189190

0 commit comments

Comments
 (0)