Bug: Phantom empty message in Telegram when sending from a third-party script

I have a script/function for sending messages to Telegram. It generally works well, but if I try to send more than 2 messages in a row from a third-party script, a phantom empty message appears. This only happens between the 2nd and 3rd messages. After the third message, there can be a couple dozen more, and no more phantom messages appear. This issue only occurs when sending from a single third-party script. If I place the sending logic within the script containing the function, this problem doesn’t happen. For now, I’ve come up with a few workarounds to deal with this phantom message: 1. Add a check for empty messages in the function. 2. Create some kind of wrapper when sending messages from a third-party script. Here is the text of my script with the message-sending function:

# Bot configuration
:global botConfig
:if ([:typeof $botConfig] != "array") do={ :set botConfig [:toarray ""] }
:set ($botConfig->"Telegram") {
    "botToken"="XXXXXXXXXXXXXXXXXXXXX";
    "mainID"="XXXXXXXXXXXX"
}

# Function to form URL
:global botUrl do={
    :global botConfig
    :return ("https://api.telegram.org/bot" . ($botConfig->"Telegram"->"botToken"))
}

# Function to serialize array to JSON
:global arrayToJson do={
    :local array $1
    :local result "{"
    :local first true
    :foreach key,value in=$array do={
        :if ($first) do={:set first false} else={:set result ($result . ",")}
        :set result ($result . "\"" . $key . "\": \"" . $value . "\"")
    }
    :set result ($result . "}")
    :return $result
}

# Function to send message
:global sendTelegramMessage
:set sendTelegramMessage do={
    :global botUrl
    :global botConfig
    :global arrayToJson
    
    :local message $1
    :local chatId $2
    
    :if ([:typeof $chatId] = "nothing" || $chatId = "") do={
        :set chatId ($botConfig->"Telegram"->"mainID")
    }
    
    # Protection against empty message
    :if ([:len $message] = 0) do={
        :put "Skipping empty message"
        :return false
    }
    
    :local data {"chat_id"=$chatId; "text"=$message}
    :local jsonString [$arrayToJson $data]
    :put "Sending: $jsonString"
    
    :local url ([$botUrl] . "/sendMessage")
    :local result [/tool fetch mode=https \
        url=$url \
        http-method=post \
        http-header-field="Content-Type: application/json" \
        http-data=$jsonString \
        output=user \
        as-value]
    :put "Result: $result"
    
    # Fix: extract data into a variable
    :local response ($result->"data")
    :put "Response: $response"
    
    :return ($result->"status" = "finished")
}

# Test sending
[$sendTelegramMessage "Test message 1"]
[$sendTelegramMessage "Another test message 2" "XXXXXXXXX"]
[$sendTelegramMessage "Test message 3"]
[$sendTelegramMessage "Test message 4"]

If I send from another script like this:

:global sendTelegramMessage
  [$sendTelegramMessage "Test message 1"]
  [$sendTelegramMessage "Another test message 2" "XXXXXXXXXX"]
  [$sendTelegramMessage "Test message 3"]
  [$sendTelegramMessage "Test message 4"]

Then a silly message appears. But if I wrap it like this:

:global sendTelegramMessage
    :put [$sendTelegramMessage "Test message 1"]
    :put [$sendTelegramMessage "Another test message 2" "XXXXXXXXXXXX"]
    :put [$sendTelegramMessage "Test message 3"]
    :put [$sendTelegramMessage "Test message 4"]

Or like this:

:global sendTelegramMessage
    :local result1 [$sendTelegramMessage "Test message 1"]
    :put "Result 1: $result1"
    :local result2 [$sendTelegramMessage "Another test message 2" "XXXXXXXXXXXX"]
    :put "Result 2: $result2"
    :local result3 [$sendTelegramMessage "Test message 3"]
    :put "Result 3: $result3"
    :local result4 [$sendTelegramMessage "Test message 4"]
    :put "Result 4: $result4"

Then the phantom message does not appear. Can someone tell me where this phantom message comes from and how to deal with it?
I have RouterOS 7.10.2

Too many mistakes and too many times to do 2 + 2
do instead (4 / 2) + (((7 + 9) / 2²) * ½)

I apologize. I respect you very much, you have done a lot for the forum, but I don't write code often. I understand that there may be an easier way to send a message, but I don't know it and do it the way I know how/invented. Could you please tell me how to do it easier and more correctly? Or give me a link to the correct option, because what I found is at least based on GET requests, which in my opinion is not correct.

There are dozens of threads on this topic on the forum.
Sorry, but I don’t use Telegram and I don’t tinker with things I can’t test.

you can use e-worm comprehensive script available at https://github.com/eworm-de/routeros-scripts you don’t have to code anything

Yes, progress does not stand still. I will try to update and test, but at first glance the risk of phantom messages remains. But I will try and write. The main thing is to update without problems )))

It's a pity that you don't use Telegram. And you're right, there are many topics on the forum about Telegram bots. But they all have their downsides. For example, tech support suggested using a script from Christian Hesse. With all due respect to the man's work, his version of the script suffers from many downsides. First of all, the complexity of installation and configuration. Too many dependencies. It's like if I wanted to drive a certain brand of car, I would have to make a separate road for it or even set up my own traffic police. In addition, in the version proposed by tech support, a string is sent to Telegram, not JSON. And in my version, I send JSON. Which is more correct according to the standard. So far, I have not met Telegram bots for Mikrotik that would send JSON. I'm not even talking about the backup in case the power goes out. In Hesseth's script, the deferred messages in the queue will disappear, but I made it so that they don't disappear. There are other differences. So not always, when it seems to you that a person wants to add 2 + 2 and get 4, he really wants it. For 2 + 2, the script of the same Heseth will do. But I want more.

Thanks for the advice. I was wrong with the first glance. The proposed version does not have such a problem. But there are a lot of others. But thanks anyway. Now I know that Mikrotik already has some ways to work with JSON. And now I have something to look at to finish what I want.

Probably this version is still subject to the same problem, only apparently it has filters for such situations. But I can be wrong about the filters, because sometimes phantom messages slip through in Hesse’s script. And it seems to be a problem (BUG) of Mikrotik. And here is a simple test for this.
Run this code and see if there are gaps or not.

:global testFunc do={ :put "Called with: $1" }
[$testFunc "One"]
[$testFunc "Two"]
[$testFunc "Three"]

I get the same result no matter how many experiments I do.

/system script run fantom
Called with: One
Called with: Two
Called with: 
Called with: Three
Called with: 4
Called with: 5
Called with: 6
Called with: 7
Called with: 8
Called with: 9

Tell me what I’m doing wrong?

You are not telling us the truth.
Only that line cannot give that result.
I have already seen errors on the second run, they depend on syntax errors made previously, but if you only show that line, no one can help you… or not?

[rex@test] > :global testFunc do={ :put “Called with: $1” }
[rex@test] > [$testFunc “One”]
Called with: One
[rex@test] > [$testFunc “Two”]
Called with: Two
[rex@test] > [$testFunc “Three”]
Called with: Three
[rex@test] >
[rex@test] > {
{… :global testFunc do={ :put “Called with: $1” }
{… [$testFunc “One”]
{… [$testFunc “Two”]
{… [$testFunc “Three”]
{… }
Called with: One
Called with: Two
Called with:
Called with: Three
[rex@test] >
[rex@test] >
Error: double execution on 2nd run, like another 2nd run without parameters.


[rex@test] > :global testFunc do={ :return “Called with: $1” }
[rex@test] > {
{… :put [$testFunc “One”]
{… :put [$testFunc “Two”]
{… :put [$testFunc “Three”]
{… }
Called with: One
Called with: Two
Called with: Three
[rex@test] >
[rex@test] > :global testFunc “nothing, just for example”
[rex@test] > {
{… :set testFunc do={ :return “Called with: $1” }
{… :put [$testFunc “One”]
{… :put [$testFunc “Two”]
{… :put [$testFunc “Three”]
{… }
Called with: One
Called with: Two
Called with: Three
[rex@test] >
The three rules for prevent that error are very simple, one mandatory, the other almost, the last one depends:

  1. :global should never be created inside any parenthesis or any subscript called inside parenthesis (updated is not the same as created)
  2. always use :return at the end of a function, “” suffice, and avoid empty :return for exit prematurely from a function.
    if is present something in function like :if (x) do={:return y} else={:return z}, this rule obviously is not applied.
  3. do not use “do { }” to completely enclose a script or similar bulls–t.

The only thing I hid was not showing the entire code, considering that the call in the script is repeated further and there is no point in it. To make it clearer, here are the screenshots. And I want to say that I apply your rules, but it looks more like fighting symptoms, not the disease. Maybe I'm wrong, unfortunately, this week has been very busy and there is no time to delve into your examples.


Did I understand correctly how the function should be formatted? Or am I doing something wrong again? I don't understand how the function, the "do" block, can't be wrapped in curly brackets "{}"

I apologize for using your nickname in the function, but I wanted to make it clear that I don't have anything like that hanging in my global variables.


Is do { } not do={...}
Read better: completely enclose a script
do {

}

////////////////////////////



script worng:

:global echo do={
    :put "Called with: $1"
    :return ""
}
[$echo "A"]
[$echo "B"]
[$echo "C"]
[$echo "D"]
[$echo "E"]
[$echo "F"]

script correct:

:global echo do={
    :return "Called with: $1"
}
:put [$echo "A"]
:put [$echo "B"]
:put [$echo "C"]
:put [$echo "D"]
:put [$echo "E"]
:put [$echo "F"]

[rex@test-v7] /system/script> export

2025-04-04 17:46:40 by RouterOS 7.16.2

software id = CHR

/system script
add dont-require-permissions=no name=script1 owner=admin policy=
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":glo
bal echo do={\r
\n :put "Called with: $1"\r
\n :return ""\r
\n}\r
\n[$echo "A"]\r
\n[$echo "B"]\r
\n[$echo "C"]\r
\n[$echo "D"]\r
\n[$echo "E"]\r
\n[$echo "F"]\r
\n"
[rex@test-v7] /system/script> run script1
Called with: A
Called with: B
Called with:
Called with: C
Called with: D
Called with: E
Called with: F
[rex@test-v7] /system/script> export

2025-04-04 17:46:40 by RouterOS 7.16.2

software id = CHR

/system script
add dont-require-permissions=no name=script1 owner=admin
policy="ftp,reboot,read,write,policy,test,password,sniff,sen
sitive,romon" source=":global echo do={\r
\n :return "Called with: $1"\r
\n}\r
\n:put [$echo "A"]\r
\n:put [$echo "B"]\r
\n:put [$echo "C"]\r
\n:put [$echo "D"]\r
\n:put [$echo "E"]\r
\n:put [$echo "F"]\r
\n"
[rex@test-v7] /system/script> run script1
Called with: A
Called with: B
Called with: C
Called with: D
Called with: E
Called with: F
[rex@test-v7] /system/script>

How to fix that? (whitout define global “echo” outside the script)

NEVER CALL TWO TIMES THE SAME FUNCTION WITHOUT USE OR ASSIGN THE RETURNED VALUE

Simply add void variable for ignore useless return from the function (return everytime something no matter if :return is used [as must be] inside)

:local void
:global echo do={
    :put "Called with: $1"
    :return ""
}
:set void [$echo "A"]
:set void [$echo "B"]
:set void [$echo "C"]
:set void [$echo "D"]
:set void [$echo "E"]
:set void [$echo "F"]

[rex@test-v7] /system/script> run script1
Called with: A
Called with: B
Called with: C
Called with: D
Called with: E
Called with: F

Rex answered you.
This is a long-known bug of RouterOS, which I described, for example, here https://habr.com/ru/articles/646663/. It concerns not your function for Telegram, but all functions in general. If you call any function twice in a row (without assigning a result), it will be executed three times! You can check it yourself on any of your functions. A simple check in the function body fixes the problem: :if ([$len $0]!=0) do={ }.

Coming here and writing “there is an alternative solution to your solution, but it is more complex” (and it doesn’t work) is not a great solution…


and something should be put in there anyway, so why not JUST :return “” at the end instead of :if ([$len $0]!=0) do={ } at the start???

and anyway $len IS WRONG, (is :len) so what test did he ever do?

:if ([$len [:nothing]]!=0) do={:put true} else={:put false}

:if ([$len ""]!=0) do={:put true} else={:put false}

:if ([$len "xxx"]!=0) do={:put true} else={:put false}

everytime is true

Am I missing something or why just not call function without placing it in square brackets [$echo “”] and there is no issue with return value if called multiple times?

:local echo do={
  :put "Called with: $1"
  <also can be here useless return>
}
$echo "A"
$echo "B"
$echo "C"
$echo "D"
$echo "E"
$echo "F"

> /system/script/run script1 
Called with: A
Called with: B
Called with: C
Called with: D
Called with: E
Called with: F

or some combination like this:

:local echo do={
  :return "Called with: $1"
}
:put [$echo "A"]
$echo
:put [$echo "B"]
$echo 1
:put [$echo "C"]
$echo "something"
:put [$echo "D"]
$echo nil
:put [$echo "E"]
$echo [:nothing]
:put [$echo "F"]

> /system/script/run script1 
Called with: A
Called with: B
Called with: C
Called with: D
Called with: E
Called with: F

Anyway, calling function inside square brackets should be used only when return value is handled…

I think you missed the previous pieces, it all started from this post:
http://forum.mikrotik.com/t/bug-phantom-empty-message-in-telegram-when-sending-from-a-third-party-script/182925/1
and we’re talking about how that script was written and why it causes errors when written that way.

Honestly, I’m tired of writing the same thing a hundred times everywhere on the forum.
Make do.
I’m tired of always having to prove everything and that people constantly try to refute (even writing wrong things before even checking them, like the post before yours).

If you’re so good at trying endlessly to disprove the obvious, you also know how to make your own scripts.

If you don’t like what I write, don’t read it.

Ok, I see, bad OP script in general… Regarring topic name, “bug” is resolvable by correctly calling functions.

Regarding useless return placeholder, I was just trying to point out that it doesn’t make affect on producing “bug” even if exists for some reason when function is called without square brackets, not related to your example.