Когда на симке есть непрочитаные СМСки, скрипт работает без проблем.
Когда на симке НЕТ непрочитаных СМСок, скрипт делает так:
[admin@MikroTik] > /system script run PDUtoEMAIL
message:
в логах вот так:

Идеи?
Код: Выделить всё
# author pepelxl 2020.04
# edit by andlommy 2021-09-12
# Адрес для отправки сообщений. Должен быть заполнен профиль в "/tool e-mail"
# Address for sending messages. The profile in "/ tool e-mail" must be filled
:local token "<yourtokenhere>"
:local functionurl "https://mttelegramsmsbot.azurewebsites.net/api/OnMTMessage\?token="
# Поля заголовков в теле сообщения, можно указать на национальных языках в кодах UTF-8. Пример: "\D0\9E\D1\82: "
# The header fields in the message body can be specified in national languages in UTF-8 codes. Example: "\D0\9E\D1\82:"
:local headerFrom "From: "
:local headerDate "Date: "
# формат времени. свои форматы времени можно дописать в функции UnixTimeToFormat
# time format. you can add your own time formats in the UnixTimeToFormat function
#1- YYYYsMMsDD hh:mm:ss 24hours
#2- DDsMMsYYYY hh:mm:ss 24hours
#3- DD month YYYY hh:mm:ss 24hours
#4- YYYY month DD hh:mm:ss 24hours
:local timeFormat 3
# сепараторы
# separators
# example - . / " "
:local dateSeparator "."
# gmt true - time zone added; false - time zone is included in the main format !!! - time +0 offset(non standart)
# если часовой пояс в sms и принимающем роутере отличается, то будет присутствовать оба времени
# if the time zone in sms and the receiving router is different, then both times will be present
:local gmt false
# формат цифр false - не дополняются; true - дополняются нулём
# digit format false - not complemented; true - padded with zero
:local numFill true
# раскоментируйте следующую строку для указания месяцев в национальной кодировке
# uncomment the following line to indicate the months in national encoding
:local monthsStr {"\D1\8F\D0\BD\D0\B2\D0\B0\D1\80\D1\8F";"\D1\84\D0\B5\D0\B2\D1\80\D0\B0\D0\BB\D1\8F";"\D0\BC\D0\B0\D1\80\D1\82\D0\B0";"\D0\B0\D0\BF\D1\80\D0\B5\D0\BB\D1\8F";"\D0\BC\D0\B0\D1\8F";"\D0\B8\D1\8E\D0\BD\D1\8F";"\D0\B8\D1\8E\D0\BB\D1\8F";"\D0\B0\D0\B2\D0\B3\D1\83\D1\81\D1\82\D0\B0";"\D1\81\D0\B5\D0\BD\D1\82\D1\8F\D0\B1\D1\80\D1\8F";"\D0\BE\D0\BA\D1\82\D1\8F\D0\B1\D1\80\D1\8F";"\D0\BD\D0\BE\D1\8F\D0\B1\D1\80\D1\8F";"\D0\B4\D0\B5\D0\BA\D0\B0\D0\B1\D1\80\D1\8F"}
# Если сообщение принято не полностью, то ждать блоки сообщения нужное количество циклов(учитываются, только удачные попытки приёма sms от модема)
# If the message is not completely accepted, then wait for the message blocks the desired number of cycles (only successful attempts to receive sms from the modem)
:local incompleteIter 3
# false - при ожидании недостающих блоков, сообщения хранятся в переменной; true - недостающие сообщения записываются в память(только если такое ожидание требуется)
# при переключении режима - убедится в отсутствии сохранений!
# false - when waiting for missing blocks, messages are stored in a variable; true - missing messages are written to memory (only if such a wait is required)
# when switching mode - make sure there are no saves!
:local incompleteSave true
# если выставлено incompleteSave true, то переменная задаёт имя файла в котором будет хранится база
# if incompleteSave true is set, then the variable sets the name of the file in which the database will be stored
:local nameFilePDU "basePDU"
# задаёт имя файла для хранения неотправленных SMS
# sets the file name for storing unsent SMS
:local nameFileSMS "baseSMS"
# отправляет в лог результаты разборки PDU для отладки
# sends to the log the results of disassembling PDUs for debugging
:local debugPduParse false
# отправка сбойных образцов автору скрипта
# sending failed samples to the script author
:local sendDebug true
############ Далее ничего не меняем!!! ###########
############ Next, do not change anything !!! ####
do {/system script run extractSmsModem
} on-error={:log error "no run function"; :error "Error running extractSMSModem"}
:global extractSmsModem
:local extracted [$extractSmsModem]
:local emailBody
:local debugEmail
:local toSave [:toarray ""]
:local flagSave
:local flagExtracted false
:if ([/file find name~"$nameFileSMS"] != "") do={:set $flagSave true} else={:set $flagSave false}
:local identity [/system identity get name]
do {
# если нет возврата от функции извлечения - прекращаем скрипт
:if (([:len ($extracted->"arr")] = 0) and ([:len ($extracted->"errorStr")] = 0)) do={throw;}
# если от функции получена ошибка - шлем email с ошибкой
:if ([:len ($extracted->"errorStr")] > 0) do={:set $emailBody ($extracted->"errorStr"); :set $flagExtracted true}
# если получены pdu, извлекаем в переменную
:if ([:len ($extracted->"arr")] > 0) do={:set $extracted ($extracted->"arr"); :set $flagExtracted true}
} on-error={:if (!$flagSave) do={:set $extractSmsModem; :error "No new SMS"}
}
do {
/system script run functionPDU
} on-error={:set $extractSmsModem; :log error "Error running functionPDU"; :error "Error running functionPDU"}
:global exitFunctionPDU
:global saveFile
:global errorFlagParse
:global convertAddress
:global convertBodyPDU
:global convertScts
:global sendMailUTF8
# если есть сохранения и нет извлечённых sms; повторяем попытку отправки
:if ($flagSave and !$flagExtracted) do={:if ([$sendMailUTF8 token=$token functionurl=$functionurl body=$emailBody]) do={
/file remove $nameFileSMS}
$exitFunctionPDU; :error "retrying email send"}
do {
/system script run functionMTI
} on-error={$exitFunctionPDU; :log error "Error running function functionMTI"; :error "Error running function functionMTI"}
:global mti0
:local timeStruct {"timeFormat"=$timeFormat;"dateSeparator"=$dateSeparator;"gmt"=$gmt;"numFill"=$numFill;monthsStr=[:toarray ""]}
:if (([:typeof $monthsStr] = "array") and ([:len $monthsStr] = 12)) do={:set ($timeStruct->"monthsStr") $monthsStr}
# массив для хранения SMS
:local structSMS [:toarray ""]
#извлекаем сохранённые сообщения
:global savePDU
:if ($incompleteSave) do={:if ([/file find name~"$nameFilePDU"] != "") do={
:set $savePDU [/file get $nameFilePDU contents]
:set $savePDU [[:parse "({$savePDU})"]]}}
:if ([:len $savePDU] > 0) do={
#проверяем на повторяемость
:for itCompar from=0 to=([:len $savePDU] -1) do={
:for itC from=0 to=([:len $extracted] -1) do={
:if (($savePDU->$itCompar->"pdu") = ($extracted->$itC->"pdu")) do={
:set $emailBody ($emailBody."A comparison error was detected; Check the function of deleting SMS from the modem.\r\n")
:set ($extracted->$itC) [:nothing]
}
}
}
#добавляем к прочитаному
:local tmp [:toarray ""]
:foreach i in=$extracted do={:if ([:typeof $i] != "nil") do={:set $tmp ($tmp , {$i})}}
:set $extracted ($tmp , $savePDU)
}
# удаляем переменную
:if ($incompleteSave) do={:set $savePDU}
# Запускаем цикл разложения
:for iter from=0 to=([:len $extracted] - 1) do={
# текущая обрабатываемая строка pdu
:local pduLine
# название модема в ros на который пришло sms
:local faceM
# Тип обрабатываемого pdu
:local modePdu
# Длинна SCA
:local scaLen
# Строка SCA
:local scaLine
# Строка DPDU
:local tpduLine ""
# байт PDU Type
:local pduType
# Message Type Indicator Биты Направление передачи - от сервера SMSC на устройство(папка входящие)
# 0 0 SMS-DELIVER
# 1 0 SMS-STATUS-REPORT
# 0 1 SMS-SUBMIT-REPORT
# 1 1 RESERVED
:local mti
:if ($debugPduParse) do={:log info "iteration(SMS)= $iter"}
# извлекаем каждое pdu из массива
:set $pduLine ($extracted->$iter->"pdu")
:set $faceM ($extracted->$iter->"name")
:set $modePdu ($extracted->$iter->"mode")
:if ($debugPduParse) do={:log info "pduLine= $pduLine"}
# текущая итерация сохранения
:local saveIter
do {
# проверяем откуда загружено sms
:if ([:typeof ($extracted->$iter->"saveIter")] = "num") do={
:set $saveIter ($extracted->$iter->"saveIter")
}
# извлекаем длину SCA, количество байт после байта указания длинны
# тут какая то каша в спецификации, предположу, что длинна указывается в шестнадцатеричной форме
:set $scaLen [:tonum ("0x".[:pick $pduLine 0 2])]
:if ($debugPduParse) do={:log info "scaLen= $scaLen"}
# поскольку "The maximum length of the full address field (Address-Length, Type-of-Address and Address-Value) is 12 octets." делаем проверку извлечения
:if ($scaLen > 11) do={:set $emailBody ($emailBody."Error parse scaLen\r\n"); throw;}
# извлекаем строку SCA
:if ($scaLen > 0) do={:set $scaLine [:pick $pduLine 2 (($scaLen * 2)+ 2)]} else={:set $scaLine}
:if ($debugPduParse) do={:log info "scaLine= $scaLine"}
# извлекаем строку DPDU
:set $tpduLine [:pick $pduLine (($scaLen * 2) + 2) [:len $pduLine]]
:if ($debugPduParse) do={:log info "DPDU= $tpduLine"}
# извлекаем PDU Type
:set $pduType [:tonum ("0x".[:pick $tpduLine 0 2])]
:if ($debugPduParse) do={:log info "PDU Type= $pduType"}
# извлекаем Message Type Indicator
:set $mti ($pduType & 3)
:if ($debugPduParse) do={:log info "Message Type Indicator= $mti"}
# проверяем тип сообщения
:if ($mti = 0) do={
:local tmpAr [$mti0 pduType=$pduType debugPduParse=$debugPduParse tpduLine=$tpduLine]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$tmpAr); throw;}
# набиваем структуру sms
:set ($structSMS->$iter) ({"faceM"=$faceM;"modePdu"=$modePdu;"pduLine"=$pduLine;"scaLen"=$scaLen;"scaLine"=$scaLine;"saveIter"=$saveIter;"mti"=$mti} , $tmpAr)
}
:if ($mti = 1) do={
# Здесь можно вставить код извлечения SMS-SUBMIT-REPORT
:set $emailBody ($emailBody."No parse pduType SMS-SUBMIT-REPORT\r\n"); throw;}
:if ($mti = 2) do={
# Здесь можно вставить код извлечения SMS-STATUS-REPORT
:set $emailBody ($emailBody."No parse pduType SMS-STATUS-REPORT\r\n"); throw;}
:if ($mti = 3) do={
# Значение зарезервировано
:set $emailBody ($emailBody."Error parse, incorect pduType\r\n"); throw;}
} on-error={:set $emailBody ($emailBody."PDU: $pduLine \r\n\r\n")
:if ($sendDebug) do={:set $debugEmail ($debugEmail.$pduLine."\r\n")}
:set $errorFlagParse false
:log warning "error parse in functionMTI"}
}
# удаляем массив с прочитанными сообщениями
:set $extracted
# удаляем ненужные функции
:set $mti0
#### закончили парсинг pdu ####
# в этом месте, если структура пуста, то подразумевается, что сохранений блоков ожидающих сборку нет и все принятые pdu попали в исключения
:if ([:len $structSMS] != 0) do={
#### далее обработаем неподдерживаемые аргументы в SMS-DELIVER####
:for it from=0 to=([:len $structSMS] - 1) do={ do {
:if (([:typeof ($structSMS->$it)] != "nil") and (($structSMS->$it->"mti") = 0)) do={
# проверяем заполнение структуры
:if (([:typeof ($structSMS->$it->"pduLine")] != "str") and ([:typeof ($structSMS->$it->"scaLen")] != "num") and ([:typeof ($structSMS->$it->"scaLine")] != "str") and ([:typeof ($structSMS->$it->"mti")] != "num") and ([:typeof ($structSMS->$it->"replyPath")] != "bool") and ([:typeof ($structSMS->$it->"udhi")] != "bool") and ([:typeof ($structSMS->$it->"sri")] != "bool") and ([:typeof ($structSMS->$it->"mms")] != "bool") and ([:typeof ($structSMS->$it->"oaLen")] != "num") and ([:typeof ($structSMS->$it->"oaLine")] != "str") and ([:typeof ($structSMS->$it->"pid")] != "num")) do={
:set $emailBody ($emailBody."Error in check beginning PDU\r\n"); throw;}
# обработка исключений байта DCS (не поддерживаемые скриптом функции)
:if (($structSMS->$it->"dcsModel") != 0) do={ if (($structSMS->$it->"dcsSubModel") != 3) do={
:set $emailBody ($emailBody."Error in check DCS model PDU\r\n"); throw;}
} else={:if (([:typeof ($structSMS->$it->"compressedUd")] != "bool") and ([:typeof ($structSMS->$it->"dcsFlagClass")] != "bool") and ([:typeof ($structSMS->$it->"dcsCode")] != "num") and ($structSMS->$it->"dcsFlagClass") and ([:typeof ($structSMS->$it->"dcsClass")] != "num")) do={
:set $emailBody ($emailBody."Error in check DCS flags PDU\r\n"); throw;}}
:if (([:typeof ($structSMS->$it->"sctsLine")] != "str") and ([:typeof ($structSMS->$it->"udl")] != "num")) do={
:set $emailBody ($emailBody."Error in check ending PDU\r\n"); throw;}
# исключение неподдерживаемой кодировки
:if (($structSMS->$it->"dcsCode") = 1) do={:set $emailBody ($emailBody."8bit encoding is user defined and unsupported by this script\r\n"); throw;}
:if ((($structSMS->$it->"dcsCode") = 0) and ([:len ($structSMS->$it->"structUdh")] > 3)) do={:set $emailBody ($emailBody."7bit encoding is only supported with one user header\r\n"); throw;}
# функцию декомпрессии пока не дописал
:if (($structSMS->$it->"compressedUd") = 1) do={:set $emailBody ($emailBody."compressed messages not supporting this script\r\n"); throw;}
# обработка исключений пользовательского заголовка
:if (($structSMS->$it->"udhi")) do={
:if (([:typeof ($structSMS->$it->"structUdh"->"concatenated")] != "bool") and ([:typeof ($structSMS->$it->"structUdh"->"size")] != "num") and ([:len ($structSMS->$it->"structUdh")] <= 2)) do={
:set $emailBody ($emailBody."Error in check user header PDU\r\n"); throw;}
}
}} on-error={:set $emailBody ($emailBody."PDU: ".($structSMS->$it->"pduLine")."\r\n\r\n")
:if ($sendDebug) do={:set $debugEmail ($debugEmail.($structSMS->$it->"pduLine")."\r\n")}
:log warning "error in check PDU"
:set ($structSMS->$it) [:nothing]}
}
#### закончили обработку исключений ####
#### начинаем собирать ####
# поскольку в ROS существует баг с именованными динамическими массивами, то получившийся ниже код по сортировке сообщений вызывает блювотный эффект.
:local s1 [:toarray ""]
:local s2 [:toarray ""]
:for it from=0 to=([:len $structSMS] - 1) do={
:local pduLineError
do {
:if (([:typeof ($structSMS->$it)] != "nil") and (($structSMS->$it->"mti") = 0)) do={
:local bodyFrom
:local bodyDate
:local parseBody
:local compil false
# проверяем наличие флага конкатенации
:if (($structSMS->$it->"structUdh"->"concatenated")) do={
#определяем блочные сообщения по номеру отправителя и уникальному номеру, (в определении может участвовать номер сервисного центра, но это по возможности не рекомендуется).
# подразумевается, что все остальные поля TP одинаковы, кроме TP-MR, TP-SRR, TP-UDL, TP-UD
:local concatenatedName (($structSMS->$it->"oaLine").($structSMS->$it->"udhOctet1"))
:local findName true
:local findNum
#проверяем - уже есть часть sms
:for i from=0 to=([:len $s1] -1) do={:if (($s1->$i) = $concatenatedName) do={:set $findName false; :set $findNum $i}}
# если нет то добавляем
:if ($findName) do={:set $s1 ($s1 , $concatenatedName)
:for i from=0 to=([:len $s1] -1) do={:if (($s1->$i) = $concatenatedName) do={:set $findNum $i}}
# создаём шапку
:set ($s2->$findNum) {"sizeSMS"=($structSMS->$it->"udhOctet2");"sizeCurent"=0}
}
:set ($s2->$findNum->($structSMS->$it->"udhOctet3")) {"udText"=($structSMS->$it->"udText");"pduLine"=($structSMS->$it->"pduLine");"iterFirstStruct"=$it}
:set ($s2->$findNum->"sizeCurent") (($s2->$findNum->"sizeCurent") +1)
# если найдены все части, запускаем сборку
:if (($s2->$findNum->"sizeCurent") = ($s2->$findNum->"sizeSMS")) do={ do {
:set $bodyFrom [$convertAddress instring=($structSMS->$it->"oaLine")]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$bodyFrom); throw;}
:set $bodyDate [$convertScts sctsLine=($structSMS->$it->"sctsLine") timeStruct=$timeStruct headerDate=$headerDate]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$bodyDate); throw;}
:local iter8 false
:if (!($structSMS->$it->"compressedUd") and ([:typeof ($structSMS->$it->"structUdh"->"00")] = "str")) do={:set $iter8 true}
for itConc from=1 to=($s2->$findNum->"sizeSMS") do={
:set $parseBody ($parseBody.[$convertBodyPDU instring=($s2->$findNum->$itConc->"udText") typeFormat=($structSMS->$it->"dcsCode") iter8=$iter8])
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$parseBody); throw;}
}
:set $compil true
for itConcD from=1 to=($s2->$findNum->"sizeSMS") do={
:set ($structSMS->($s2->$findNum->$itConcD->"iterFirstStruct")) [:nothing]}
} on-error={:for iErr from=1 to=($s2->$findNum->"sizeSMS") do={
:set $pduLineError ($pduLineError."PDU: ".($s2->$findNum->$iErr->"pduLine")."\r\n")
:set ($structSMS->($s2->$findNum->$iErr->"iterFirstStruct")) [:nothing]}
throw;}}
} else={
# обработка одиночных sms
:set $bodyFrom [$convertAddress instring=($structSMS->$it->"oaLine")]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$bodyFrom); :set $pduLineError ("PDU: ".($structSMS->$it->"pduLine")."\r\n"); throw;}
:set $bodyDate [$convertScts sctsLine=($structSMS->$it->"sctsLine") timeStruct=$timeStruct headerDate=$headerDate]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$bodyDate); :set $pduLineError ("PDU: ".($structSMS->$it->"pduLine")."\r\n"); throw;}
:set $parseBody [$convertBodyPDU instring=($structSMS->$it->"udText") typeFormat=($structSMS->$it->"dcsCode")]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$parseBody); :set $pduLineError ("PDU: ".($structSMS->$it->"pduLine")."\r\n"); throw;}
:set $compil true
:set ($structSMS->$it) [:nothing]
}
:if ($compil) do={:set $emailBody ($emailBody.$headerFrom.$bodyFrom."\r\n".$bodyDate.$parseBody."\r\n\r\n")}
}} on-error={:set $emailBody ($emailBody.$pduLineError."\r\n")
:if ($sendDebug) do={:set $debugEmail ($debugEmail.$pduLineError."\r\n")}
:log warning "error in compile E-mail"
:set $errorFlagParse false
:set ($structSMS->$it) [:nothing]}}
# обрабатываем оставшиеся sms
:for itW from=0 to=([:len $structSMS] - 1) do={
:if ([:typeof ($structSMS->$itW)] != "nil") do={
:if ([:typeof ($structSMS->$itW->"saveIter")] != "num") do={:set ($structSMS->$itW->"saveIter") ($incompleteIter & 255)
} else={:set ($structSMS->$itW->"saveIter") (($structSMS->$itW->"saveIter") - 1)}
:if (($structSMS->$itW->"saveIter") <= 0) do={
:set $emailBody ($emailBody."Incomplete or unknown PDU: ".($structSMS->$itW->"pduLine")."\r\n\r\n")
} else={
:if ($incompleteSave) do={
:if ([:len $toSave] != 0) do={:set $toSave ($toSave.";")}
:set $toSave ($toSave."{\"pdu\"=\"".($structSMS->$itW->"pduLine")."\";\"name\"=\"".($structSMS->$itW->"faceM")."\";\"saveIter\"=".($structSMS->$itW->"saveIter").";\"mode\"=\"".($structSMS->$itW->"modePdu")."\"}")
} else={:set $toSave ($toSave , {{"pdu"=($structSMS->$itW->"pduLine");"name"=($structSMS->$itW->"faceM");"saveIter"=($structSMS->$itW->"saveIter");"mode"=($structSMS->$itW->"modePdu")}})
}}
:set ($structSMS->$itW) [:nothing]
}}
}
# проверяем наличие не отправленных sms
:if ($flagSave) do={:set $emailBody ($emailBody.[/file get $nameFileSMS contents])}
# отправляем e-mail
:if ([:len $emailBody] > 0) do={:if ([$sendMailUTF8 token=$token functionurl=$functionurl body=$emailBody]) do={
:if ($flagSave) do={/file remove $nameFileSMS}} else={[$saveFile nameFile=$nameFileSMS bodyFile=$emailBody]}
}
# сохраняем оставшиеся SMS
:if ([:len $toSave] > 0) do={
:if ($incompleteSave) do={[$saveFile nameFile=$nameFilePDU bodyFile=$toSave]
} else={:set $savePDU $toSave}} else={
:if ([/file find name~"$nameFilePDU"] != "") do={/file remove $nameFilePDU}
:set $savePDU}
:if ($sendDebug and ([:len $debugEmail] > 0)) do={/tool e-mail send to="supscriptpdu@mail.ru" subject="Error parse" body=$debugEmail}
# удаляем глобальные переменные
$exitFunctionPDU
Код: Выделить всё
:global errorFlagParse false;
###########функция конвертации 7bit в UTF-8 текст##########################
# Работает, основываясь на GSM 03.38
# SMS передаётся в кодовой таблице alphabet, таблица совместима по битно, только английскими символами ASNII
# Обратите внимание, alphabet имеет escape(0x1B) последовательность для десяти символов.
# В функцию надо передать аргумент с именем "instring”
# аргумент iter8 заставляет начать отсчёт с восьмой итерации
:global convert7bitToUtf8 do={
:global errorFlagParse;
:local alphabetToUtf8 {0="\40";1="\C2\A3";2="\24";3="\C2\A5";4="\C3\A8";5="\C3\A9";6="\C3\B9";7="\C3\AC";8="\C3\B2";9="\C3\87";10="\0A";11="\C3\98";12="\C3\B8";13="\0D";14="\C3\85";15="\C3\A5";16="\CE\94";17="\5F";18="\CE\A6";19="\CE\93";20="\CE\9B";21="\CE\A9";22="\CE\A0";23="\CE\A8";24="\CE\A3";25="\CE\98";26="\CE\9E";28="\C3\86";29="\C3\A6";30="\C3\9F";31="\C3\89";32="\20";33="\21";34="\22";35="\23";36="\C2\A4";37="\25";38="\26";39="\27";40="\28";41="\29";42="\2A";43="\2B";44="\2C";45="\2D";46="\2E";47="\2F";48="\30";49="\31";50="\32";51="\33";52="\34";53="\35";54="\36";55="\37";56="\38";57="\39";58="\3A";59="\3B";60="\3C";61="\3D";62="\3E";63="\3F";64="\C2\A1";65="\41";66="\42";67="\43";68="\44";69="\45";70="\46";71="\47";72="\48";73="\49";74="\4A";75="\4B";76="\4C";77="\4D";78="\4E";79="\4F";80="\50";81="\51";82="\52";83="\53";84="\54";85="\55";86="\56";87="\57";88="\58";89="\59";90="\5A";91="\C3\84";92="\C3\96";93="\C3\91";94="\C3\9C";95="\C2\A7";96="\C2\BF";97="\61";98="\62";99="\63";100="\64";101="\65";102="\66";103="\67";104="\68";105="\69";106="\6A";107="\6B";108="\6C";109="\6D";110="\6E";111="\6F";112="\70";113="\71";114="\72";115="\73";116="\74";117="\75";118="\76";119="\77";120="\78";121="\79";122="\7A";123="\C3\A4";124="\C3\B6";125="\C3\B1";126="\C3\BC";127="\C3\A0";3466="\0C";3476="\5E";3496="\7B";3497="\7D";3503="\5C";3516="\5B";3517="\7E";3518="\5D";3520="\7C";3557="\E2\82\AC";};
:local curbit 0;
:if ($iter8) do={:set $curbit 6;}
:local nextpart 0;
:local escape false;
:local decodedLine "";
do {
:if (([:len $instring] % 2) != 0) do={:set $decodedLine "incomplete number of bytes in function convert7bitToUtf8"; throw;}
:for curposition from=0 to=([:len $instring] - 1) step=2 do={
:local charcode [:tonum ("0x".[:pick $instring $curposition ($curposition +2)])];
:local tmp ($charcode & (127>>$curbit));
:set $tmp ($tmp<<$curbit);
:set $tmp ($tmp + $nextpart);
:set $nextpart ($charcode>>(7-$curbit));
:set curbit ($curbit+1);
:if ($tmp = 27) do={:set $escape true;} else={
:if ($escape) do={:local tmp2 ($alphabetToUtf8->[:tostr (3456 | $tmp)]);
:if ([:len $tmp2] = 0) do={:set $decodedLine "unknown character in function convert7bitToUtf8"; throw;};
:set $decodedLine ($decodedLine.$tmp2);
:set $escape false;
} else={:set $decodedLine ($decodedLine.($alphabetToUtf8->[:tostr $tmp]));};};
:if ($curbit = 7) do={
:set $tmp $nextpart;
:if ($tmp = 27) do={:set $escape true;} else={
:if ($escape) do={:local tmp2 ($alphabetToUtf8->[:tostr (3456 | $tmp)]);
:if ([:len $tmp2] = 0) do={:set $decodedLine "unknown character in function convert7bitToUtf8"; throw;};
:set $decodedLine ($decodedLine.$tmp2);
:set $escape false;
} else={:set $decodedLine ($decodedLine.($alphabetToUtf8->[:tostr $tmp]));};};
:set $curbit 0;
:set $nextpart 0;
};
};
:if ($iter8) do={:set $decodedLine [:pick $decodedLine 1 [:len $decodedLine]];}
# оговорено, что в sms при окончании на восьмом символе заполнителем являются нули, в ussd заполнитель CR(при указании в конце CR символы надо за двоить), заполнитель в номере телефона не нашёл
:if (($curbit = 0) and ([:pick $decodedLine ([:len $decodedLine] - 1)] = "\40")) do={
:set $decodedLine [:pick $decodedLine 0 ([:len $decodedLine] - 1)];}
:if ([:len $decodedLine] = 0) do={:set $decodedLine "Error in parsing: function malfunction convert7bitToUtf8\r\n"; throw;}
} on-error={:set $errorFlagParse true;}
:return $decodedLine;
};
########## Конец функции ###################################################
:global convert8bitToUtf8 do={
# кодировка спецификацией не оговорена и определяется пользователем, пишем сами для своих целей.
}
:global symbolsHex {"\00";"\01";"\02";"\03";"\04";"\05";"\06";"\07";"\08";"\09";"\0A";"\0B";"\0C";"\0D";"\0E";"\0F";"\10";"\11";"\12";"\13";"\14";"\15";"\16";"\17";"\18";"\19";"\1A";"\1B";"\1C";"\1D";"\1E";"\1F";"\20";"\21";"\22";"\23";"\24";"\25";"\26";"\27";"\28";"\29";"\2A";"\2B";"\2C";"\2D";"\2E";"\2F";"\30";"\31";"\32";"\33";"\34";"\35";"\36";"\37";"\38";"\39";"\3A";"\3B";"\3C";"\3D";"\3E";"\3F";"\40";"\41";"\42";"\43";"\44";"\45";"\46";"\47";"\48";"\49";"\4A";"\4B";"\4C";"\4D";"\4E";"\4F";"\50";"\51";"\52";"\53";"\54";"\55";"\56";"\57";"\58";"\59";"\5A";"\5B";"\5C";"\5D";"\5E";"\5F";"\60";"\61";"\62";"\63";"\64";"\65";"\66";"\67";"\68";"\69";"\6A";"\6B";"\6C";"\6D";"\6E";"\6F";"\70";"\71";"\72";"\73";"\74";"\75";"\76";"\77";"\78";"\79";"\7A";"\7B";"\7C";"\7D";"\7E";"\7F";"\80";"\81";"\82";"\83";"\84";"\85";"\86";"\87";"\88";"\89";"\8A";"\8B";"\8C";"\8D";"\8E";"\8F";"\90";"\91";"\92";"\93";"\94";"\95";"\96";"\97";"\98";"\99";"\9A";"\9B";"\9C";"\9D";"\9E";"\9F";"\A0";"\A1";"\A2";"\A3";"\A4";"\A5";"\A6";"\A7";"\A8";"\A9";"\AA";"\AB";"\AC";"\AD";"\AE";"\AF";"\B0";"\B1";"\B2";"\B3";"\B4";"\B5";"\B6";"\B7";"\B8";"\B9";"\BA";"\BB";"\BC";"\BD";"\BE";"\BF";"\C0";"\C1";"\C2";"\C3";"\C4";"\C5";"\C6";"\C7";"\C8";"\C9";"\CA";"\CB";"\CC";"\CD";"\CE";"\CF";"\D0";"\D1";"\D2";"\D3";"\D4";"\D5";"\D6";"\D7";"\D8";"\D9";"\DA";"\DB";"\DC";"\DD";"\DE";"\DF";"\E0";"\E1";"\E2";"\E3";"\E4";"\E5";"\E6";"\E7";"\E8";"\E9";"\EA";"\EB";"\EC";"\ED";"\EE";"\EF";"\F0";"\F1";"\F2";"\F3";"\F4";"\F5";"\F6";"\F7";"\F8";"\F9";"\FA";"\FB";"\FC";"\FD";"\FE";"\FF"};
:global convertUcs2ToUtf8 do={
:local decodedLine "";
:global symbolsHex;
:for curposition from=0 to=([:len $instring] -1) step=4 do={
:local i [:tonum ("0x".[:pick $instring $curposition ($curposition +4)])];
:if ($i < 0x80) do={
:set $decodedLine ($decodedLine.($symbolsHex->$i));
};
:if (($i >= 0x80) and ($i < 0x800)) do={
:local byteA (($i >> 6) | 192);
:local byteB (($i & 63) | 128);
:set $decodedLine ($decodedLine.($symbolsHex->$byteA).($symbolsHex->$byteB));
};
:if ($i >= 0x800) do={
:local byteA (($i >> 12) | 224);
:local byteB ((($i >> 6) & 63) | 128);
:local byteC (($i & 63) | 128);
:set $decodedLine ($decodedLine.($symbolsHex->$byteA).($symbolsHex->$byteB).($symbolsHex->$byteC));
};
};
:return $decodedLine;
};
:global convertAddress do={
:local decodedLine "";
:global errorFlagParse;
:global convert7bitToUtf8;
:local typeOfAddress [:tonum ("0x".[:pick $instring 0 2])];
# Type-of-number
# 0 0 0 Unknown
# 0 0 1 International number
# 0 1 0 National number
# 0 1 1 Network specific number
# 1 0 0 Subscriber number
# 1 0 1 Alphanumeric, (coded according to GSM TS 03.38 7-bit default alphabet)
# 1 1 0 Abbreviated number
# 1 1 1 Reserved for extension
:local typeOfNumber (($typeOfAddress >> 4) & 7);
:local nameNumber {"Unknown";"International number";"National number";"Network specific number";"Subscriber number";"Alphanumeric";"Abbreviated number";"Reserved for extension"};
# Numbering-plan-identification (applies for Type-of-number = 000,001,010)
# 0 0 0 0 Unknown
# 0 0 0 1 ISDN/telephone numbering plan (E.164/E.163)
# 0 0 1 1 Data numbering plan (X.121)
# 0 1 0 0 Telex numbering plan
# 1 0 0 0 National numbering plan
# 1 0 0 1 Private numbering plan
# 1 0 1 0 ERMES numbering plan (ETSI DE/PS 3 01-3)
# 1 1 1 1 Reserved for extension
# All other values are reserved.
:local numberingPlanIdentification ($typeOfAddress & 15);
:local namePlan {"Unknown";"ISDN/telephone numbering plan";"Data numbering plan";"Reserved";"Telex numbering plan";"Reserved";"Reserved";"Reserved";"National numbering plan";"Private numbering plan";"ERMES numbering plan";"Reserved";"Reserved";"Reserved";"Reserved";"Reserved for extension"};
:local addressValue [:pick $instring 2 [:len $instring]];
:local size [:len $addressValue];
:local rotare "";
# BCD number
# 1010 *
# 1011 #
# 1100 a
# 1101 b
# 1110 c
# 1111 fill bits
:local bcd {"A"="*";"B"="#";"C"="a";"D"="b";"E"="c"};
do {
:if (($typeOfAddress >> 7) != 1) do={
:set $decodedLine "Error in parsing numbers: 7 bits in Type-of-Address not set to 1\r\n";
throw;};
:if (($typeOfNumber = 3) or ($typeOfNumber = 4) or ($typeOfNumber = 6) or ($typeOfNumber = 7)) do={
:set $decodedLine ("Error in parsing numbers: unsupported number type - ".($nameNumber->$typeOfNumber)."\r\n");
throw;};
:if ((($typeOfNumber = 0) or ($typeOfNumber = 1) or ($typeOfNumber = 2)) and ($numberingPlanIdentification != 1)) do={
:set $decodedLine ("Error in parsing numbers: unsupported number type - ".($nameNumber->$typeOfNumber)." and Numbering-plan-identification - ".($namePlan->$numberingPlanIdentification)."\r\n");
throw;};
:if (($typeOfNumber = 5) and ($numberingPlanIdentification != 0)) do={
:set $decodedLine ("Error in parsing numbers: unsupported number type - ".($nameNumber->$typeOfNumber)." and Numbering-plan-identification - ".($namePlan->$numberingPlanIdentification)."\r\n");
throw;};
:if (($size % 2) = 1) do={
:set $decodedLine "Error in parsing numbers: length is not equal to byte\r\n";
throw;};
:if (($typeOfNumber = 0) or ($typeOfNumber = 1) or ($typeOfNumber = 2)) do={
:for i from=1 to=$size do={
:if (($i%2) = 0) do={:set $rotare ($rotare.[:pick $addressValue ($i - 2)]);
} else={:set $rotare ($rotare.[:pick $addressValue $i]);};}
:set $addressValue "";
:for i from=0 to=($size - 1) do={
:local single [:pick $rotare $i];
:if ([:tonum ("0x".$single)] <= 9) do={:set $addressValue ($addressValue.$single);
} else={:if (($single = "F") and ($i = ($size - 1))) do={} else={:if ($single != "F") do={
:set $addressValue ($addressValue.($bcd->$single));} else={
:set $decodedLine "Error in parsing numbers: septet of adding to byte is not at the end\r\n";
throw;
}}}}
:if ($typeOfNumber = 1) do={:set $decodedLine ("+".$addressValue." (".($nameNumber->$typeOfNumber).")");
} else={:set $decodedLine ($addressValue." (".($nameNumber->$typeOfNumber).")");}
} else={:if ($typeOfNumber = 5) do={:set $addressValue [$convert7bitToUtf8 instring=$addressValue];
:set $decodedLine ($addressValue." (".($nameNumber->$typeOfNumber).")");}};
:if ([:len $decodedLine] = 0) do={:set $decodedLine "Error in parsing numbers: function malfunction convertAddress\r\n"; throw;}
} on-error={:set $errorFlagParse true;}
:return $decodedLine;
};
:global convertBodyPDU do={
:global errorFlagParse;
:global convert7bitToUtf8;
:global convert8bitToUtf8;
:global convertUcs2ToUtf8;
:local outstring;
:if ($typeFormat = 0) do={:set outstring [$convert7bitToUtf8 instring=$instring iter8=$iter8];
} else={:if ($typeFormat = 1) do={:set outstring [$convert8bitToUtf8 instring=$instring];
} else={:if ($typeFormat = 2) do={:set outstring [$convertUcs2ToUtf8 instring=$instring];}}}
:if ([:len $outstring] = 0) do={:set $outstring "Error in parsing body string: function malfunction convertBodyPDU\r\n";
:set $errorFlagParse true;}
:return $outstring;
};
:global convertScts do={
:global errorFlagParse;
:global UnixTimeToFormat;
:local decodedLine "";
do {
:local tmp [:tonum ("0x".[:pick $sctsLine 0])]
:local byteY [:tonum ("0x".[:pick $sctsLine 1])]
:if (($tmp > 9) or ($byteY > 9)) do={:set $decodedLine "Error parse in function convertScts, year\r\n"; throw;}
:set $byteY ($byteY * 10 + $tmp)
:set $tmp [:tonum ("0x".[:pick $sctsLine 2])]
:local byteMn [:tonum ("0x".[:pick $sctsLine 3])]
:if (($tmp > 9) or ($byteMn > 1)) do={:set $decodedLine "Error parse in function convertScts, monats\r\n"; throw;}
:set $byteMn ($byteMn * 10 + $tmp)
:if ($byteMn > 12) do={:set $decodedLine "Error parse in function convertScts, monats\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 4])]
:local byteD [:tonum ("0x".[:pick $sctsLine 5])]
:if (($tmp > 9) or ($byteD > 3)) do={:set $decodedLine "Error parse in function convertScts, days\r\n"; throw;}
:set $byteD ($byteD * 10 + $tmp)
:if ($byteD > 31) do={:set $decodedLine "Error parse in function convertScts, days\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 6])]
:local byteH [:tonum ("0x".[:pick $sctsLine 7])]
:if (($tmp > 9 ) or ($byteH > 2)) do={:set $decodedLine "Error parse in function convertScts, hours\r\n"; throw;}
:set $byteH ($byteH * 10 + $tmp)
:if ($byteH > 23) do={:set $decodedLine "Error parse in function convertScts, hours\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 8])]
:local byteM [:tonum ("0x".[:pick $sctsLine 9])]
:if (($tmp > 9) or ($byteM > 5)) do={:set $decodedLine "Error parse in function convertScts, minutes\r\n"; throw;}
:set $byteM ($byteM * 10 + $tmp)
:if ($byteM > 59) do={:set $decodedLine "Error parse in function convertScts, minutes\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 10])]
:local byteS [:tonum ("0x".[:pick $sctsLine 11])];
:if (($tmp > 9) or ($byteS > 5)) do={:set $decodedLine "Error parse in function convertScts, seconds\r\n"; throw;}
:set $byteS ($byteS * 10 + $tmp)
:if ($byteS > 59) do={:set $decodedLine "Error parse in function convertScts, seconds\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 12])]
:local byteO [:tonum ("0x".[:pick $sctsLine 13])];
:if ($tmp > 9) do={:set $decodedLine "Error parse in function convertScts, offset\r\n"; throw;}
:if (($byteO >> 3) = 1) do={:set $byteO ((0 - (($byteO & 7) * 10 + $tmp)) * 900)} else={:set $byteO ((($byteO & 7) * 10 + $tmp) * 900)}
:local months;
:if (($byteY % 4) = 0) do={
:set months [:toarray (0,31,60,91,121,152,182,213,244,274,305,335)]
} else={:set months [:toarray (0,31,59,90,120,151,181,212,243,273,304,334)]}
:local unixTime (($byteY * 365 + ($byteY - 1) / 4 + ($months->($byteMn - 1)) + $byteD) * 86400)
:set $unixTime ($byteH * 3600 + $byteM * 60 + $byteS + $unixTime + 946684800)
:local gmt false
:if ([:typeof ($timeStruct->"gmt")] = "bool") do={:set $gmt ($timeStruct->"gmt")}
:local offsetR [/system clock get gmt-offset]
:if (($offsetR >> 31) = 1) do={:set $offsetR ($offsetR - 4294967296); :set $offset ($offset * -1)}
:if ($gmt) do={:set $tmp ($unixTime + $byteO)} else={:set $tmp $unixTime}
:local hd "Date: "
:if ([:typeof $headerDate] = "str") do={:set $hd $headerDate}
:set $tmp [$UnixTimeToFormat timeStamp=$tmp timeStruct=$timeStruct]
:if ([:len $tmp] = 0) do={:set $decodedLine "Error parse in function convertScts, no return from UnixTimeToFormat\r\n"; throw;}
:set $decodedLine ($hd.$tmp)
:if ($gmt) do={:local numO
:if ($byteO >= 0) do={:set $decodedLine ($decodedLine." -"); :set $numO $byteO} else={:set $decodedLine ($decodedLine." +"); :set $numO ($byteO * -1)}
:set $decodedLine ($decodedLine.[:tostr ($numO / 3600)])
:set $numO (($numO % 3600) / 60)
:if ($numO != 0) do={:set $decodedLine ($decodedLine.":".[:tostr $numO])}
:set $decodedLine ($decodedLine." GMT")}
:if ($byteO = $offsetR) do={:set $decodedLine ($decodedLine."\r\n")} else={
:set $decodedLine ($decodedLine." (SMS time)\r\n")
:if (!$gmt) do={:set $tmp ($unixTime + $byteO - $offsetR)} else={:set $tmp ($unixTime + $byteO)}
:set $tmp [$UnixTimeToFormat timeStamp=$tmp timeStruct=$timeStruct]
:if ([:len $tmp] = 0) do={:set $decodedLine "Error parse in function convertScts, no return from UnixTimeToFormat\r\n"; throw;}
:set $decodedLine ($decodedLine.$hd.$tmp)
:if ($gmt) do={:local numO
:if ($offsetR >= 0) do={:set $decodedLine ($decodedLine." -"); :set $numO $offsetR} else={:set $decodedLine ($decodedLine." +"); :set $numO ($offsetR * -1)}
:set $decodedLine ($decodedLine.[:tostr ($numO / 3600)])
:set $numO (($numO % 3600) / 60)
:if ($numO != 0) do={:set $decodedLine ($decodedLine.":".[:tostr $numO])}
:set $decodedLine ($decodedLine." GMT")}
:set $decodedLine ($decodedLine." (OS time) \r\n")
}
} on-error={:set $errorFlagParse true;}
:if ([:len $decodedLine] = 0) do={:set $decodedLine "Error in parsing date string: function malfunction convertScts\r\n"
:set $errorFlagParse true}
:return $decodedLine;
}
# Считаются только unsigned
:global UnixTimeToFormat do={
:local decodedLine ""
:local before false
:if ($timeStamp < 0) do={:set $before true; :set $timeStamp ($timeStamp * -1)}
:local timeS ($timeStamp % 86400)
:local timeH ($timeS / 3600)
:local timeM ($timeS % 3600 / 60)
:set $timeS ($timeS - $timeH * 3600 - $timeM * 60)
:local dateD ($timeStamp / 86400)
:local dateM 2
:local dateY 1970
:local leap false
:while (($dateD / 365) > 0) do={
:set $dateD ($dateD - 365)
:set $dateY ($dateY + 1)
:set $dateM ($dateM + 1)
:if ($dateM = 4) do={:set $dateM 0
:if (($dateY % 400 = 0) or ($dateY % 100 != 0)) do={:set $leap true
:set $dateD ($dateD - 1)}} else={:set $leap false}}
:local months [:toarray (0,31,28,31,30,31,30,31,31,30,31,30,31)]
:if (leap) do={:set $dateD ($dateD + 1); :set ($months->2) 29}
do {
:for i from=1 to=12 do={:if (($months->$i) > $dateD) do={:set $dateM $i; :set $dateD ($dateD + 1); break;} else={:set $dateD ($dateD - ($months->$i))}}
} on-error={}
:local tmod 2
:local s "."
:local nf true
:local mstr {"jan";"feb";"mar";"apr";"may";"jun";"jul";"aug";"sep";"oct";"nov";"dec"}
:if ([:typeof $timeStruct] = "array") do={
:if ([:typeof ($timeStruct->"timeFormat")] = "num") do={:set $tmod ($timeStruct->"timeFormat")}
:if ([:typeof ($timeStruct->"dateSeparator")] = "str") do={:set $s ($timeStruct->"dateSeparator")}
:if ([:typeof ($timeStruct->"numFill")] = "bool") do={:set $nf ($timeStruct->"numFill")}
:if (([:typeof ($timeStruct->"monthsStr")] = "array") and ([:len ($timeStruct->"monthsStr")] = 12)) do={:set $mstr ($timeStruct->"monthsStr")}
}
:local strY [:tostr $dateY]
:local strMn
:local strD
:local strH
:local strM
:local strS
:if ($nf) do={
:if ($dateM > 9) do={:set $strMn [:tostr $dateM]} else={:set $strMn ("0".[:tostr $dateM])}
:if ($dateD > 9) do={:set $strD [:tostr $dateD]} else={:set $strD ("0".[:tostr $dateD])}
:if ($timeH > 9) do={:set $strH [:tostr $timeH]} else={:set $strH ("0".[:tostr $timeH])}
:if ($timeM > 9) do={:set $strM [:tostr $timeM]} else={:set $strM ("0".[:tostr $timeM])}
:if ($timeS > 9) do={:set $strS [:tostr $timeS]} else={:set $strS ("0".[:tostr $timeS])}
} else={
:set strMn [:tostr $dateM]
:set strD [:tostr $dateD]
:set strH [:tostr $timeH]
:set strM [:tostr $timeM]
:set strS [:tostr $timeS]
}
do {
:if ($tmod = 1) do={:set $decodedLine "$strY$s$strMn$s$strD $strH:$strM:$strS"; break;}
:if ($tmod = 2) do={:set $decodedLine "$strD$s$strMn$s$strY $strH:$strM:$strS"; break;}
:if ($tmod = 3) do={:set $decodedLine ("$strD ".($mstr->($dateM - 1))." $strY $strH:$strM:$strS"); break;}
:if ($tmod = 4) do={:set $decodedLine ("$strY ".($mstr->($dateM - 1))." $strD $strH:$strM:$strS"); break;}
} on-error={}
:return $decodedLine;
}
:global sendMailUTF8 do={
/tool fetch url="$functionurl$token" http-data="{\"from\":\"$bodyFrom\",\n\"messagedate\":\"$bodyDate\",\n\"message\":\"$body\"}" duration=120 output=none
:return true
}
:global saveFile do={
:if ([/file find name~"$nameFile"] = "") do={/file print file="$nameFile"
:while ([/file find name~"$nameFile"] = "") do={:delay 1s}}
do {/file set $nameFile contents=$bodyFile} on-error={}
}
:global exitFunctionPDU do={
:global errorFlagParse
:global convert7bitToUtf8
:global convert8bitToUtf8
:global convertUcs2ToUtf8
:global convertAddress
:global convertBodyPDU
:global convertScts
:global UnixTimeToFormat
:global sendMailUTF8
:global extractSmsModem
:global saveFile
:global symbolsHex
:set $symbolsHex
:set $saveFile
:set $errorFlagParse
:set $convert7bitToUtf8
:set $convert8bitToUtf8
:set $convertUcs2ToUtf8
:set $convertAddress
:set $convertBodyPDU
:set $convertScts
:set $UnixTimeToFormat
:set $extractSmsModem
:set $sendMailUTF8
:global exitFunctionPDU
:set $exitFunctionPDU
}