Если кому-то еще нужен скрипт, работает без AT-команд, напрямую через /tool sms inbox, поэтому должен работать с любимы модемами совместимыми с Mikrotik, для отправка email должен быть настроен Tool|Email и указан email для отправки local emailTo "КУДА_ОТПРАВЛЯТЬ_EMAIL", для выключения удаления sms после отправки сбросить флаг local autodelete 1
Код: Выделить всё
# SMS Forwarder — Версия 7. Чтение /tool sms inbox + PDU + UTF-8 + склейка частей.
:local autodelete 1;
:local emailTo "КУДА_ОТПРАВЛЯТЬ_EMAIL";
:local sysName [/system identity get name];
:local translit 1;
:local hexStr "0123456789ABCDEF";
:local asciiStr " !\"#\$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_-abcdefghijklmnopqrstuvwxyz{|}~";
# --- таблицы символов (UTF-16 -> UTF-8 байты) ---
:local symbolsUTF [:toarray "2013,000A,0020,0021,0022,0023,0024,0025,0026,0027,0028,0029,002A,002B,002C,002D,002E,002F,0030,0031,0032,0033,0034,0035,0036,0037,0038,0039,003A,003B,003C,003D,003E,003F,0040,0041,0042,0043,0044,0045,0046,0047,0048,0049,004A,004B,004C,004D,004E,004F,0050,0051,0052,0053,0054,0055,0056,0057,0058,0059,005A,005B,005C,005D,005E,005F,0060,0061,0062,0063,0064,0065,0066,0067,0068,0069,006A,006B,006C,006D,006E,006F,0070,0071,0072,0073,0074,0075,0076,0077,0078,0079,007A,007B,007C,007D,007E,00AB,00BB"];
:local symbolsRusUTF [:toarray "0410,0411,0412,0413,0414,0415,0401,0416,0417,0418,0419,041A,041B,041C,041D,041E,041F,0420,0421,0422,0423,0424,0425,0426,0427,0428,0429,042A,042B,042C,042D,042E,042F,0430,0431,0432,0433,0434,0435,0451,0436,0437,0438,0439,043A,043B,043C,043D,043E,043F,0440,0441,0442,0443,0444,0445,0446,0447,0448,0449,044A,044B,044C,044D,044E,044F"];
:local symbolsRus [:toarray "\D0\90,\D0\91,\D0\92,\D0\93,\D0\94,\D0\95,\D0\81,\D0\96,\D0\97,\D0\98,\D0\99,\D0\9A,\D0\9B,\D0\9C,\D0\9D,\D0\9E,\D0\9F,\D0\A0,\D0\A1,\D0\A2,\D0\A3,\D0\A4,\D0\A5,\D0\A6,\D0\A7,\D0\A8,\D0\A9,\D0\AA,\D0\AB,\D0\AC,\D0\AD,\D0\AE,\D0\AF,\D0\B0,\D0\B1,\D0\B2,\D0\B3,\D0\B4,\D0\B5,\D1\91,\D0\B6,\D0\B7,\D0\B8,\D0\B9,\D0\BA,\D0\BB,\D0\BC,\D0\BD,\D0\BE,\D0\BF,\D1\80,\D1\81,\D1\82,\D1\83,\D1\84,\D1\85,\D1\86,\D1\87,\D1\88,\D1\89,\D1\8A,\D1\8B,\D1\8C,\D1\8D,\D1\8E,\D1\8F,_,!,_,_,_,%,&,',(,),*,+,_,-,.,/,0,1,2,3,4,5,6,7,8,9"];
# --- словарь для склейки частей ---
:local parts [:toarray ""];
:local partOrder [:toarray ""];
:foreach msgId in=[/tool sms inbox find] do={
:local phone [/tool sms inbox get $msgId phone];
:local pdu [/tool sms inbox get $msgId pdu];
:local ts [/tool sms inbox get $msgId timestamp];
:local text "";
:local ok 0;
:local total 1;
:local part 1;
:local ref "0";
# ===== 1. находим DCS и начало данных =====
:local scaLenByte [:pick $pdu 0 2];
:local scaLen ([:find $hexStr [:pick $scaLenByte 0 1]]*16 + [:find $hexStr [:pick $scaLenByte 1 2]]);
:local pos 0;
:if ($scaLen>0) do={ :set pos ($pos + 2 + $scaLen*2); } else={ :set pos 2; }
:set pos ($pos + 2);
:set pos ($pos + 2);
:set pos ($pos + 2);
:local dcs "";
:for k from=3 to=16 do={
:if ($ok=0) do={
:local testPos ($pos + $k*2);
:if (($testPos+20) <= [:len $pdu]) do={
:local pidByte [:pick $pdu ($testPos-2) $testPos];
:local dcsByte [:pick $pdu $testPos ($testPos+2)];
:if (($pidByte = "00") and (($dcsByte="08") or ($dcsByte="04") or ($dcsByte="00") or ($dcsByte="07") or ($dcsByte="0F"))) do={
:local sctsOk 1;
:for s from=0 to=13 step=2 do={
:local b [:pick $pdu ($testPos+2+$s) ($testPos+4+$s)];
:if ([:typeof [:find "0123456789" [:pick $b 0 1]]] = "nil" or [:typeof [:find "0123456789" [:pick $b 1 2]]] = "nil") do={ :set sctsOk 0; }
}
:if ($sctsOk=1) do={
:set ok 1;
:set dcs $dcsByte;
:set pos ($testPos + 2 + 14);
}
}
}
}
}
:if ($ok=1) do={
# ===== 2. UDL и UDH =====
:set pos ($pos + 2);
:local udhlByte [:pick $pdu $pos ($pos+2)];
:local udhl ([:find $hexStr [:pick $udhlByte 0 1]]*16 + [:find $hexStr [:pick $udhlByte 1 2]]);
:if ($udhl>=1 and $udhl<=15) do={
:local ieiByte [:pick $pdu ($pos+2) ($pos+4)];
:local iei ([:find $hexStr [:pick $ieiByte 0 1]]*16 + [:find $hexStr [:pick $ieiByte 1 2]]);
:if ($iei<=15) do={
# читаем ref/total/part из UDH
:if ($iei=0) do={
:set total ([:find $hexStr [:pick $pdu ($pos+8) ($pos+9)]]*16 + [:find $hexStr [:pick $pdu ($pos+9) ($pos+10)]]);
:set part ([:find $hexStr [:pick $pdu ($pos+10) ($pos+11)]]*16 + [:find $hexStr [:pick $pdu ($pos+11) ($pos+12)]]);
:set ref ([:pick $pdu ($pos+6) ($pos+8)]);
} else={
:set total ([:find $hexStr [:pick $pdu ($pos+10) ($pos+11)]]*16 + [:find $hexStr [:pick $pdu ($pos+11) ($pos+12)]]);
:set part ([:find $hexStr [:pick $pdu ($pos+12) ($pos+13)]]*16 + [:find $hexStr [:pick $pdu ($pos+13) ($pos+14)]]);
:set ref ([:pick $pdu ($pos+6) ($pos+10)]);
}
:set pos ($pos + 2 + $udhl*2);
}
}
:local dataHex [:pick $pdu $pos [:len $pdu]];
# ===== 3. декодирование =====
:if ($dcs = "08" or $dcs = "04") do={
# UCS-2 big-endian
:for i from=0 to=([:len $dataHex]-1) step=4 do={
:local char [:pick $dataHex $i ($i+4)];
:local ind [:find $symbolsRusUTF $char];
:if ([:typeof $ind]="num") do={
:set text ($text.[:pick $symbolsRus $ind]);
} else={
:set ind [:find $symbolsUTF $char];
:if ([:typeof $ind]="num") do={
:local hexc [:pick $symbolsUTF $ind];
:if ($hexc = "0020") do={ :set text ($text."\20") }
:if ($hexc = "000A") do={ :set text ($text."\0A") }
:if ($hexc = "2013") do={ :set text ($text."\E2\80\93") }
:if ([:tonum ("0x".$hexc)] >= 0x21 and [:tonum ("0x".$hexc)] <= 0x7E) do={
:local asc [:pick $asciiStr ([:tonum ("0x".$hexc)] - 32)];
:set text ($text.$asc);
}
}
}
}
} else={
# 7-bit
:local curbit 0;
:local nextpart 0;
:local smsDecoded "";
:for i from=0 to=([:len $dataHex]-1) step=2 do={
:local tmp [:pick $dataHex $i];
:local charcode ([:find "0123456789ABCDEF" $tmp]*16);
:set tmp [:pick $dataHex ($i+1)];
:set charcode ($charcode+[:find "0123456789ABCDEF" $tmp]);
:if ($curbit<7) do={
:set tmp ($charcode & (127>>$curbit));
:set tmp ($tmp<<$curbit);
:set tmp ($tmp + $nextpart);
:set nextpart ($charcode>>(7-$curbit));
:set curbit ($curbit+1);
}
:set smsDecoded ($smsDecoded.[:pick $asciiStr ($tmp-32)]);
:if ($curbit=7) do={
:set tmp $nextpart;
:set curbit 0;
:set nextpart 0;
:set smsDecoded ($smsDecoded.[:pick $asciiStr ($tmp-32)]);
}
}
:set text $smsDecoded;
# обратная транслитерация
:if ($translit=1) do={
:local trLat {"shch";"sch";"ch";"sh";"zh";"kh";"ts";"yu";"ya";"yo";"ju";"ja";"jo";"j";
"a";"b";"v";"g";"d";"e";"z";"i";"y";"k";"l";"m";"n";"o";"p";"r";"s";"t";"u";"f";"c";"h";"w";"x";"q";"'" }
:local trCyr {"\D1\89";"\D1\89";"\D1\87";"\D1\88";"\D0\B6";"\D1\85";"\D1\86";"\D1\8E";"\D1\8F";"\D1\91";"\D1\8E";"\D1\8F";"\D1\91";"\D0\B9";
"\D0\B0";"\D0\B1";"\D0\B2";"\D0\B3";"\D0\B4";"\D0\B5";"\D0\B7";"\D0\B8";"\D1\8B";"\D0\BA";"\D0\BB";"\D0\BC";"\D0\BD";"\D0\BE";"\D0\BF";"\D1\80";"\D1\81";"\D1\82";"\D1\83";"\D1\84";"\D1\86";"\D1\85";"\D0\B2";"\D0\BA\D1\81";"\D0\BA";"\D1\8C" }
:local res2 "";
:local j 0;
:local smsLen2 [:len $text];
:while ($j < $smsLen2) do={
:local found 0;
:for m from=0 to=([:len $trLat]-1) do={
:if ($found=0) do={
:local pat [:pick $trLat $m];
:local patLen [:len $pat];
:if (($j+$patLen) <= $smsLen2) do={
:if ([:pick $text $j ($j+$patLen)] = $pat) do={
:set res2 ($res2.[:pick $trCyr $m]);
:set j ($j+$patLen);
:set found 1;
}
}
}
}
:if ($found=0) do={
:set res2 ($res2.[:pick $text $j]);
:set j ($j+1);
}
}
:set text $res2;
}
}
} else={
:set text "PDU parse error";
}
# ===== 4. склейка / одиночное =====
:if ($total>1) do={
:local key ($phone."#".$ref);
:local idx [:find $partOrder $key];
:if ([:typeof $idx] != "num") do={
:set partOrder ($partOrder , $key);
:set ($parts->$key) {"total"=$total;"count"=0;"phone"=$phone;"ts"=$ts;"texts"=[:toarray ""]};
}
:local cur ($parts->$key);
:if ([:typeof ($cur->"texts"->$part)] != "str") do={
:set ($cur->"texts"->$part) $text;
:set ($cur->"count") (($cur->"count")+1);
}
:set ($parts->$key) $cur;
} else={
:local dd [:pick $ts 8 10];
:local mm [:pick $ts 5 7];
:local yyyy [:pick $ts 0 4];
:local hh [:pick $ts 11 19];
:local datePrefix ("[" . $dd . "." . $mm . "." . $yyyy . " " . $hh . "]");
:local subject ($sysName." - SMS From ".$phone);
:local body ($sysName." ".$datePrefix." SMS from ".$phone.": ".$text);
/tool e-mail send to=$emailTo subject=($subject."\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8") body=$body;
}
}
# ===== 5. отправка склеенных многочастных =====
:foreach key in=$partOrder do={
:local cur ($parts->$key);
:if (($cur->"count") = ($cur->"total")) do={
:local phone ($cur->"phone");
:local ts ($cur->"ts");
:local full "";
:for i from=1 to=($cur->"total") do={ :set full ($full.($cur->"texts"->$i)); }
:local dd [:pick $ts 8 10];
:local mm [:pick $ts 5 7];
:local yyyy [:pick $ts 0 4];
:local hh [:pick $ts 11 19];
:local datePrefix ("[" . $dd . "." . $mm . "." . $yyyy . " " . $hh . "]");
:local subject ($sysName." - SMS From ".$phone);
:local body ($sysName." ".$datePrefix." SMS from ".$phone.": ".$full);
/tool e-mail send to=$emailTo subject=($subject."\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8") body=$body;
}
}
# ===== 6. удаление обработанных =====
:if ($autodelete=1) do={
:foreach msgId in=[/tool sms inbox find] do={ /tool sms inbox remove $msgId; }
}