Disclaimer: эта история не про код, а про использование ИИ для упрощения и ускорения написания мелких pet-проектов (и не только).

Я живу в деревне и как практически в любой деревне на постсоветском пространстве у нас интернет идёт через модем с сим-картой.

900x1200.webp
В моём случае это NR-400

У моего модема на борту есть LUCI.

Возникла необходимость читать смски с этого модема не заходя в web-интерфейс (слишком много времени на это тратится). Но писать самому этот код не очень хотелось. Поэтому решил использовать ChatGPT как помощника.

Задача №1. Получить смски на модеме

Найти код в самом модеме отвечающий за чтение смсок. Тут я справился сам (т.к. уже копался в потрохах LUCI на этом модеме). Нашёл команду которая читает все смс: at -S modem at-sms-list all. Результат был в виде json данных. Дальше встал вопрос парсинга данных и перекодировки самих сообщений.

Задача №2. Разобрать данные

Из кода web-интерфейса я узнал что значат часть полей json. Но хотелось узнать что значат все поля. Задал этот вопрос чату. И он на удивление всё расписал как надо:

Hidden text

Данные в предоставленной JSON строке представляют собой информацию о SMS-сообщениях, включая метаданные и содержимое сообщений. Вот как расшифровать структуру данных:

  1. "mem": Объект с информацией о памяти.

    • "type": Тип памяти ("ME" - внутренняя память телефона).

    • "used": Использованная память в единицах.

    • "total": Общий объем памяти в единицах.

  2. "sms": Список объектов SMS-сообщений.

    • "idx": Индекс сообщения.

    • "stat": Статус сообщения ("read" - прочитано).

    • "fail": Признак ошибки отправки ("False" - не было ошибки).

    • "smsc": Сервисный центр SMS.

    • "sender": Отправитель сообщения.

    • "ts": Временная метка сообщения.

    • "date": Дата сообщения.

    • "time": Время сообщения.

    • "zone": Часовой пояс сообщения.

    • "c_ref": Счетчик ссылок (если есть).

    • "c_cur": Текущий номер сообщения.

    • "c_tot": Общее количество частей сообщения.

    • "dcs": Кодировка данных.

    • "data": Содержимое SMS в виде строки.

Каждый объект в списке "sms" представляет одно SMS-сообщение. Важные поля:

  • "sender": Имя отправителя.

  • "ts", "date", "time", "zone": Дата и время отправки сообщения с учетом часового пояса.

  • "data": Содержимое сообщения (текст).

Кодировка "dcs" указывает на способ кодирования данных в поле "data". Значение 2 указывает на кодировку UCS-2 (16-бит Unicode), которая обычно используется для текстовых сообщений на кириллице.

Обратите внимание, что часть информации может быть неполной или сокращенной, так как эти данные представляют собой сжатую версию длинных SMS-сообщений.

Если у вас есть какие-либо дополнительные вопросы или вы хотите более подробно разобраться с каким-либо аспектом данных, пожалуйста, уточните.

Задача №3. Написать рабочий код

Пришло время писать код. Но писать с нуля не очень хотелось. Поэтому был взят код из web-интерфейса модема, написанного на JS. Попросил чат конвертировать этот код из JS в PHP (лучше наверно в python, но опыта маловато). Он сделал это, но предупредил что часть кода пропала, т.к. в PHP не получится работать с DOM деревом страницы????.

Дальше я попросил его переписать код для работы с объектами, а не массивами (в исходном JS коде все json данные брались через синтаксис массива).

Следующим этапом было упростить одну из функций, для увеличения удобочитаемости (кому интересно функция smsConcatenate). С чем он справился вполне не плохо.

Дальше я поигрался с выводом таблицы в html. Но это так, ради интереса. Интересное в этом то, что весь этот код тоже дописывался ИИ.

Hidden text
<!DOCTYPE html>
<html>
<head>
    <title>SMS Data</title>
    <style>
        body {
            font-family: Arial, sans-serif;
        }
        table {
            border-collapse: collapse;
            width: 100%;
        }
        th, td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }
        th {
            background-color: #f2f2f2;
        }
        tr:nth-child(even) {
            background-color: #f2f2f2;
        }
    </style>
</head>
<body>

<?php

function decodeUnicode($sub) {
    // ...
}

function smsTranslate($sms) {
    // ...
}

function smsCompare($a, $b) {
    // ...
}

function smsConcatenate($messages) {
    // ...
}

// Загрузка данных из файла test.json
$jsonData = file_get_contents('test.json');
$smsData = json_decode($jsonData);

// Обработка и сортировка SMS
$processedSms = smsConcatenate($smsData);

// Вывод таблицы с данными
echo '<table>
        <tr>
            <th>idx</th>
            <th>stat</th>
            <th>fail</th>
            <th>smsc</th>
            <th>sender</th>
            <th>ts</th>
            <th>date</th>
            <th>time</th>
            <th>zone</th>
            <th>c_ref</th>
            <th>c_cur</th>
            <th>c_tot</th>
            <th>dcs</th>
            <th>data</th>
        </tr>';

foreach ($processedSms as $sms) {
    echo '<tr>';
    echo '<td>' . $sms->idx . '</td>';
    echo '<td>' . $sms->stat . '</td>';
    echo '<td>' . $sms->fail . '</td>';
    echo '<td>' . $sms->smsc . '</td>';
    echo '<td>' . $sms->sender . '</td>';
    echo '<td>' . $sms->ts . '</td>';
    echo '<td>' . $sms->date . '</td>';
    echo '<td>' . $sms->time . '</td>';
    echo '<td>' . $sms->zone . '</td>';
    echo '<td>' . $sms->c_ref . '</td>';
    echo '<td>' . $sms->c_cur . '</td>';
    echo '<td>' . $sms->c_tot . '</td>';
    echo '<td>' . $sms->dcs . '</td>';
    echo '<td>' . $sms->data . '</td>';
    echo '</tr>';
}

echo '</table>';
?>

</body>
</html>

Пришло время сделать то, ради чего всё начиналось.

Я удалил всё что связанно с html и сделал отправку в телеграмм через Apprise.

$client = new Client(env('APPRISE_URL'), $base_auth);
$content =
			$newSms->data . PHP_EOL .
			'==========' . PHP_EOL .
			'<strong>Номер</strong>: <code>' . $newSms->smsc . '</code>' . PHP_EOL.
			'<strong>Дата</strong>: ' . $newSms->date . ' ' . $newSms->time;
content, $newSms->sender))
->setFormat(Format::HTML)
->telegram(
$_ENV['TELEGRAM_TOKEN'],
$_ENV['TELEGRAM_CHAT_ID']
)
->send();

Попросил чат разбить весь код на отдельные файлы в соответствии с ООП. Пришлось немного поправить вручную, т.к. разделение по файлам прошло не совсем точно.

Ещё была задача придумать название пакета???? с чем он в целом тоже справился.

Теперь нужно было заменить работу с файлом json на выполнение команды на удалённом компе (модеме) по ssh. Тут всё было предсказуемо. Пришлось немного побороться с ssh подключением, но в целом ничего сложного.

И на этом всё. Всё работает, смски приходят. Правда чуть позже я добавил ещё отправку определённых смсок (от МЧС) на MQTT-брокер (для Home Assistant).

Итого

Весь код можно разбить на следующие части:

  • 30% кода это исходный вариант на js;

  • 60% кода написано ChatGPT;

  • 10% кода написано мной.

Так же важно упомянуть что всё это заняло около 2-3 часов. При учёте что я, по большей части, занимался проверкой кода, на не его написанием.

И как мне кажется использование ChatGPT для таких мелких pet-проектов выглядет очень привлекательно.

Исходный код получившегося проекта можно глянуть тут.

Снимок экрана от 2023-08-11 14-54-35.png
Пример «смс» в телеграмме

P.S. Это, фактически, третий раз когда я использовал ИИ для написания каких-то мелких личных задач. Первые две, написанные на питоне с нуля, это: получение данных с модема о текущем операторе (модем поддерживает две симки и умеет автоматически переключаться между ними) и отправка инфы в HA; получение с модема статистики по трафику и также отправка этих данных в HA (отправляются download, upload и сумма).

UPD

Исходный код js, который нужно было сконвертировать в PHP
function smsTranslate(sms) {
	var text;
	if (sms.dcs == 2) {
		text = decodeUnicode(sms.data)
		sms.data = text
	}
	return sms;
}

function smsCompare(a, b) {
	if (a.ts < b.ts)
		return 1;
	if (a.ts > b.ts)
		return -1;

	return 0;
}

function smsConcatenate(sms) {
	var obj = [];
	for (let i = 0; i < sms.length; i++) {
		if (sms[i]) {
			if (sms[i].c_ref && !sms[i].fail) {
				let total = sms[i].c_tot;
				let cur = 1;
				let tmp = sms[i];
				let fnd = true;
				while (fnd && total-- > 0) {
					fnd = false;
					for (let j = i; j < sms.length; j++) {
						if (sms[j] && (tmp.c_ref == sms[j].c_ref) && (sms[j].c_cur == cur)) {
							if (cur == 1)
								tmp = sms[j];
							else
								tmp.data = tmp.data + sms[j].data;
							cur++;
							sms[j] = null;
							fnd = true;
							break;
						}
					}
				}
				if (cur == (tmp.c_tot + 1))
					obj.push(smsTranslate(tmp));
			}
			else {
				obj.push(smsTranslate(sms[i]));
			}
		}
	}
	obj.sort(smsCompare);
	return obj;
}

function smsRead(btn) {
	var ac = document.getElementById('table_sms');
	var mem = document.getElementById('memory');

	if (!ac || !mem)
		return;

	while(ac.rows.length > 1 )
		ac.rows[0].parentNode.deleteRow(1);

	var tr = ac.rows[0].parentNode.insertRow(-1);
	tr.className = 'cbi-section-table-row cbi-rowstyle-2';
	var td = tr.insertCell(-1);
	td.colSpan = 4;
	td.innerHTML = '<em>Чтение...</em>';
	mem.innerHTML = '';
	if (btn)
		btn.disabled = true;
	stxhr.get('/cgi-bin/luci/admin/services/modem_read_sms/all?block=0&', null,
	function(x, info) {
		if (info && info.code == 0 && info.out && info.out.mem && info.out.sms) {
			mem.innerHTML = String.format('Память: %d/%d %s', info.out.mem.used, info.out.mem.total, info.out.mem.type);
			td.innerHTML = '<em>OK</em>';
			var sms = smsConcatenate(info.out.sms);
			if (sms.length == 0) {
				td.innerHTML = '<em>Нет сообщений</em>';
			}
			else {
				while (ac.rows.length > 1)
					ac.rows[0].parentNode.deleteRow(1);

				for (let i = 0; i < sms.length; i++) {
					let tr = ac.rows[0].parentNode.insertRow(-1);
					tr.className = (i % 2 == 0) ? 'cbi-section-table-row cbi-rowstyle-2' : tr.className = 'cbi-section-table-row cbi-rowstyle-1';
					if (sms[i].fail) {
						tr.insertCell(-1).innerHTML = '<br /><br /><br /br>';
						tr.insertCell(-1).innerHTML = '';
						tr.insertCell(-1).innerHTML = 'Error decode SMS';
					}
					else {
						let date = '<em>' + sms[i].time + '<br />' + sms[i].date + '<br />' + sms[i].zone + '</em>';
						let sender = sms[i].sender;
						let text = sms[i].data;
						date = '<small style="font-weight: inherit;">' + date + '</small>';
						sender = '<small style="font-weight: inherit;color: blue;">' + sender + '</small>';
						text = '<small style="font-weight: inherit;">' + text + '</small>';
						/* unread sms
						date = '<small style="font-weight: bold;">' + date + '</small>';
						sender = '<small style="font-weight: bold;color: blue;">' + sender + '</small>';
						text = '<small style="font-weight: bold;">' + text + '</small>';
						*/
						tr.insertCell(-1).innerHTML = date;
						tr.insertCell(-1).innerHTML = sender;
						tr.insertCell(-1).innerHTML = text;
					}
					tr.cells[2].align = "left";
				}
			}
		}
		else {
			td.innerHTML = '<em>Не удалось прочитать сообщения</em>';
		}
		if (btn)
			btn.disabled = false;
	});
}

function smsDelete(btn) {
	var ac = document.getElementById('table_sms');
	var mem = document.getElementById('memory');

	if (ac && mem) {
		while( ac.rows.length > 1 )
			ac.rows[0].parentNode.deleteRow(1);

		var tr = ac.rows[0].parentNode.insertRow(-1);
		tr.className = 'cbi-section-table-row cbi-rowstyle-2';
		var td = tr.insertCell(-1);
		td.colSpan = 4;
		td.innerHTML = '<em>Удаление SMS...</em>';
		mem.innerHTML = '';
		btn.disabled = true;

		stxhr.get('/cgi-bin/luci/admin/services/modem_del_sms/all', null,
		function(x, info) {
			if ((info) && (info.code == 0)) {
				td.innerHTML = '<em>SMS сообщения удалены</em>';
				smsRead(null);
			}
			else
				td.innerHTML = '<em>Ошибка удаления SMS</em>';

			btn.disabled = false;
		});
	}
}

Комментарии (6)


  1. action52champion
    13.08.2023 04:45

    Выглядит как будто статью писал ИИ.

    • Промтов, скриншотов диалога с ИИ нет

    • Зачем перегонять код из JS в PHP - непонятно, особенно учитывая что у вас в гитхабе лежат несколько проектов на Vue.js

    • исходного кода на JS, который был перегнан в PHP тоже нет, чтобы оценить качество трансляции.


    1. andreybold Автор
      13.08.2023 04:45

      Статью писал сам, думаю ИИ лучше бы написал ). Про скриншоты и диалоги — не думаю что здесь они уместны, т.к. это была бы портянка, а не статья.
      Перегонять нужно было для того, чтобы потом этот скрипт запустить на сервере через крон.
      Про исходный js не подумал, спасибо за уточнение.


      1. Rishquer
        13.08.2023 04:45

        Но js можно было бы запустить черезез node js, естественно убрав\заменив те части кода которые требуют браузерное api.


        1. andreybold Автор
          13.08.2023 04:45

          Не хотелось ещё и ноду разворачивать на серваке. PHP уже был там. К тому же, как я и писал в статье, лучше всего было бы конвертировать в питон, т.к. он есть практически на любом линуксе по умолчанию. Но в данном случае мне больше подошёл php (опыт и знания в нём были больше чем в js и питоне).


  1. Pooper123
    13.08.2023 04:45

    Шикарно. ИИ очень упрощает жизнь и обычным людям, и программистам. Учитывая скорости его развития, мне кажется, лет через десять большая часть интеллектуальных должностей будет связана либо с проверкой и доработкой того, что написал ИИ, либо с усовершенствованием самого ИИ.


  1. tormozedison
    13.08.2023 04:45

    Я как-то попробовал автоматически сгенерировать код для имитатора 6Е3П на SSD1306. Получилось нечто не имеющее ничего общего с тем, что требовалось. Потом забил на этот проект.