Старенький USB модем HUAWEI (марку не буду разглашать) перестал стабильно висеть на одном COM порту и временами переподключался на другие порты, совсем отключался и терял антенну.
Да и ситуация с библиотекой GSMComm была непонятной и болезненной.
GSMComm — это пакет для телефонов GSM, в основном для выполнения задач, связанных с SMS.Поиск по интерент показал, что есть возможность использовать встроенный функционал WEB API новых модемов HUAWEI, более эффективно, чем старый подход с AT командами реализованный в GSMComm.
www.nuget.org/packages/GSMComm последня версия 1.21.1 от 10.10.2015 года.
Выяснилось, что есть прекрасный usb модем HUAWEI E3372, который почти хакерским способом способен отправлять СМС как из скрипта (Curl + Bash), так и из кода (Python, Perl), и, как я предположил, из C#.
Самое печальное, что компания HUAWEI не предоставляет никакой документации как это сделать и все найденные методы имели экспериментальный харктер и зависели от семейства устройств.
В общем, опираясь на найденный материал, не гарантирующий работу кода с момедом, был приобретен HUAWEI E3372.
Не углублясь в эксперименты с Python или Perl я решил попробовать разобраться с вариантами Bash + Curl.
В общем, после нескольких экспериментов был найден работающий код под MS Windows 10 + Git Bash for MS Windows.
Скрипт
curl -b session.txt -c session.txt http://192.168.8.1/html/index.html > /dev/null 2>&1
#TOKEN=$(curl -s -b session.txt -c session.txt http://192.168.8.1/html/smsinbox.html)
TOKEN=$(echo $TOKEN | cut -d'"' -f 10)
echo $TOKEN > token.txt
NUMBER=$1
MESSAGE=$2
LENGTH=${#MESSAGE}
TIME=$(date +"%Y-%m-%d %T")
TOKEN=$(<token.txt)
SMS="<?xml version='1.0' encoding='UTF-8'?><request><Index>-1</Index><Phones><Phone>$NUMBER</Phone></Phones><Sca/><Content>$MESSAGE</Content><Length>$LENGTH</Length><Reserved>1</Reserved><Date>$TIME</Date></request>"
echo $SMS
curl -v -b session.txt -c session.txt -H "X-Requested-With: XMLHttpRequest" --data "$SMS" http://192.168.8.1/api/sms/send-sms --header "__RequestVerificationToken: $TOKEN" --header "Content-Type:text/xml" --header "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" --header "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3"
Скрипт удивительным образом работал. И это было уже счастье, так как деньги были потрачены на модем не зря!
Осталось только понять КАК же он работает. Почитав документацию по Curl и Bash (ну по bash я не читал так догадался) прояснилась работа скрипта. Привожу этот же скрипт с моими комментариями.
Скрипт с комментариями
# https://stackoverflow.com/questions/28070500/grab-current-sessions-cookie-with-curl/28070870
# Содержимое файла session.txt определяется опцией -b
# Сделать GET запрос и получить куки в первый раз и записать их в файл
curl -b session.txt -c session.txt http://192.168.8.1/html/index.html > /dev/null 2>&1
# Сделать GET запрос и получить куки во второй раз и записать в файл и сохранить содержимое страницы HTML в переменную TOKEN как строку
TOKEN=$(curl -b session.txt -c session.txt http://192.168.8.1/html/smsinbox.html)
# Извлчеь из переменной значение метатега из <meta name="csrf_token" content="b/XNeODpHCthQXEOEjBNkICn2n7e9v4e"/> и перезаписать в ту же переменную TOKEN
TOKEN=$(echo $TOKEN | cut -d'"' -f 10)
# Отобразить на экране
echo "$TOKEN"
# сохранить подстроку в файле
echo $TOKEN > token.txt
# Получить два параметра командной строки: (1) номер телефона и (2) текст СМС
NUMBER=$1
MESSAGE=$2
# Получить количество символов в тексте
LENGTH=${#MESSAGE}
# Получить текущее время и отформатировать его
TIME=$(date +"%Y-%m-%d %T")
# Загрузить содержимое файла в переменную
TOKEN=$(<token.txt)
# Сфоромировать текст для отправки СМС как XML
SMS="<?xml version='1.0' encoding='UTF-8'?><request><Index>-1</Index><Phones><Phone>$NUMBER</Phone></Phones><Sca/><Content>$MESSAGE</Content><Length>$LENGTH</Length><Reserved>1</Reserved><Date>$TIME</Date></request>"
# Отобразить переменную с текстом СМС на экране
echo $SMS
# Сделать POST для отправки СМС
curl -v -b session.txt -c session.txt -H "X-Requested-With: XMLHttpRequest" --data "$SMS" http://192.168.8.1/api/sms/send-sms --header "__RequestVerificationToken: $TOKEN" --header "Content-Type:text/xml"
Скажу честно, что СМС на русском я не смог добиться. Приходит абракадабра. Так что этот вопрос остался открытым и если у кого-то есть желание закрыть тему отправки СМС на русском — милости просим, дерзайте.
Понимание работы скрипта принесло свою пользу и приблизило к написанию кода на C#.
Было понятно, что в нем должно быть также 3 запроса и должны они делать то же самое что и благословенный Curl. Поэтому в коде приведен Curl, а ниже, аналогичный ему C# код.
Код С# для WinForms
private void button1_Click(object sender, EventArgs e)
{
var ip = "192.168.8.1"; // IP адрес который выдает модем в браузере после установки
var phone = "+70000000000"; // Номер телефона
var msg = "Привет!!! СМС работает!!!";
var result = SendSMS(ip, phone, msg);
if (result)
{
//TODO Сохранить в БД, например
}
else
{
//TODO Сохранить в БД, например
}
}
private bool SendSMS(string ip, string phone, string msg)
{
try
{
/* curl -b session.txt -c session.txt http://192.168.8.1/html/index.html > /dev/null 2>&1 */
Cookie firstCookie = null;
Cookie secondCookie = null;
string token = string.Empty;
//В первый раз получить куки
var cookieContainer = new CookieContainer();
var uri = new Uri($"http://{ip}/html/index.html");
using (var httpClientHandler = new HttpClientHandler { CookieContainer = cookieContainer })
{
using (var httpClient = new HttpClient(httpClientHandler))
{
httpClient.GetAsync(uri).Wait();
var all = cookieContainer.GetCookies(uri);
firstCookie = all[0];
}
}
/*
TOKEN=$(curl -s -b session.txt -c session.txt http://192.168.8.1/html/smsinbox.html)
TOKEN=$(echo $TOKEN | cut -d'"' -f 10)
echo $TOKEN > token.txt
*/
// И спользуя куки из первого запроса получить страницу и извлечь из нее токен
if (firstCookie != null)
{
var cookieContainer2 = new CookieContainer();
cookieContainer2.Add(firstCookie); // Поместить в конейнер куки из первого запроса к сайту
var uri2 = new Uri($"http://{ip}/html/smsinbox.html");
using (var httpClientHandler = new HttpClientHandler
{
CookieContainer = cookieContainer2
})
{
using (var httpClient = new HttpClient(httpClientHandler))
{
var html = httpClient.GetStringAsync(uri2).Result; // Получить страницу HTML
var all = cookieContainer2.GetCookies(uri2);
secondCookie = all[0];
var doc = new HtmlAgilityPack.HtmlDocument(); // Используем HtmlAgilityPack чтобы преобразовать текст HTML в структурный вид
doc.LoadHtml(html);
var items = doc.DocumentNode.SelectNodes("//meta");
if (items.Count >= 2) // Получить второй по счету meta тег.
{
token = items[1].GetAttributeValue("content", ""); // Получить значение метатега. Не спрашивайтепочему второй метатаг с токеном рабочий - не знаю )))
}
}
}
// Когда в наличии есть куки и токен делаем отправку СМС через запрос POST
if (!string.IsNullOrEmpty(token))
{
var msgLength = msg.Length;
var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); // TIME=$(date +"%Y-%m-%d %T")
var sms = $"<?xml version='1.0' encoding='UTF-8'?><request><Index>-1</Index><Phones><Phone>{phone}</Phone></Phones><Sca/><Content>{msg}</Content><Length>{msgLength}</Length><Reserved>1</Reserved><Date>{time}</Date></request>";
/*# Сделать POST для отправки СМС
curl -v -b session.txt -c session.txt -H "X-Requested-With: XMLHttpRequest" --data "$SMS"
http://192.168.8.1/api/sms/send-sms --header "__RequestVerificationToken: $TOKEN" --header "Content-Type:text/xml"
*/
var uri3 = new Uri($"http://{ip}/api/sms/send-sms");
var client = new RestSharp.RestClient { BaseUrl = uri3 }; // Используем RestSharp для запроса (дело вкуса)
var request = new RestSharp.RestRequest(RestSharp.Method.POST);
// Формируем свой заголовой запроса - ничего лишненго все по примеру из Curl
request.AddHeader("__RequestVerificationToken", token);
var ses = secondCookie.ToString();
request.AddCookie("cookie", ses);
request.AddHeader("Content-Type", "text/xml");
request.AddHeader("X-Requested-With", "XMLHttpRequest");
request.AddParameter("text/html", sms, RestSharp.ParameterType.RequestBody);
RestSharp.IRestResponse response = client.Execute(request);
if (response.IsSuccessful)
{
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(response.Content); // <?xml version="1.0" encoding="UTF-8"?><response> OK </response>
var responseElemenets = xmlDoc.GetElementsByTagName("response");
var resultOK = responseElemenets[0].InnerXml.ToLower();
return resultOK == "ok"; // Ну вот и признак того, что СМС отправлено, но без отчета о доставке.
}
}
}
}
catch (Exception)
{
//TODO в лог ошибку;
}
return false;
}
Как только у вас в руках рабочий C# код вы всегда можете его улучшить.
В моем случае он работает как часы и для некоторого солидного количества СМС в минуту вполне годится.
Надеюсь эта статься принесла пользу и в профессиональном и экономическом смыслах.
Душевно благодарю!
fOCUS_VRN
1)сделать метод async, чтобы избавиться от wait
2)не собирать xml как интерполяцию, т.к. если у вас в тексте будет например кавычка или символ <, xml будет невалидна
DmitryNBoyko Автор
Это не конечный вариант кода. )))
1)сделать метод async, чтобы избавиться от wait
Конечно, на усмотрение разработчика
2)не собирать xml как интерполяцию, т.к. если у вас в тексте будет например кавычка или символ <, xml будет невалидна
Ну тут все жестко, другой код и не приходит, кавычки исключены, все вшито в софт модема
Смело можете доработать и запускать платный сервис по отправке СМС! )))