TNXQSO.com — восстановленная спецификация API

Неофициальная техническая спецификация, восстановленная по клиентам TNXLOG и наблюдаемым HTTP-ответам.

Версия документа: 1.0 (сводная), 2026-10-17, UTC
Тип документа: неофициальная техническая спецификация, восстановленная по клиентам TNXLOG и наблюдаемым HTTP-ответам.
Назначение: разработка совместимого клиента, интеграция с RDA LogFlow и RDA Logger, дальнейшее исследование всех методов TNXQSO.

Граница достоверности. «Окончательная» означает единый итоговый документ по собранным к настоящему времени свидетельствам, а не доказанную полноту закрытого серверного API. Официальная OpenAPI/документация владельца TNXQSO не получена. Некоторые маршруты обнаружены в клиентском коде, но не проверены на живом сервере. Неподтверждённые поля и маршруты не должны считаться гарантированными.

1. Источники и шкала подтверждения

Код

Значение

HTTP

На сервере получен фактический HTTP-ответ; известны метод, дата, статус и некоторые метаданные.

Android

Обнаружено в Android TNXLOG 2.8 (Retrofit/DEX), включая модели запросов/ответов.

Windows

Подтверждено IL-кодом Windows TNXLOG 0.4.16 (MSI/monodis).

Node-RED

Реализация обнаружена в пользовательском экспорте flow; её наличие не доказывает текущий успех запроса.

Web JS

Структура обнаружена в JavaScript сайта TNXQSO.

Вывод

Обоснованная интерпретация источников, не измеренный серверный контракт.

Неизвестно

Данных недостаточно.

Исходные свидетельства: TNXLOG_2.8_APKPure.xapk, tnxlog.msi, http_methods.txt, il_focused.txt, manifest.json, экспорт Node-RED «Вставленный текст(4).txt», js_contexts.json, public_scan.json, tnxqso_api_check.json. В предыдущих отчётах содержатся точные смещения DEX и диапазоны IL. Данные авторизации и пользовательские значения из Node-RED в документ не воспроизводятся.

Реальные HTTP-проверки (2026-10-17 UTC)

Метод

URL

Факт

GET

/static/js/defaultStationStatus.json

HTTP 200, JSON-объект; ключи get, loc, online, rafa, rda, userFields, wff.

GET

/static/js/qth_now_locations.json

HTTP 200, JSON-массив; на момент проверки 5 элементов.

POST

/aiohttp/login

HTTP 200, JSON-объект с токеном и пользовательскими настройками. Пароль/токен из отчёта исключены.

Данные проверок не включают успешную реальную отправку QSO, удаление, геолокацию, чат или аудиозаписи.

2. Базовый URL, обмен и авторизация

  • Основной HTTPS-хост: https://tnxqso.com.
  • Для Windows TNXLOG базовый путь API: https://tnxqso.com/aiohttp/; URI собирается конкатенацией базового адреса и относительного пути (Windows, IL 34038–34051).
  • JSON: Android использует Retrofit; Windows вызывает Newtonsoft.Json.JsonConvert.SerializeObject(data) и new StringContent(json) (Windows, IL 33732–33747).
  • Особенность Windows: стандартный StringContent(string) задаёт text/plain; charset=utf-8, а не application/json (вывод из .NET). Сетевые заголовки не перехватывались. Для независимого клиента предпочтителен application/json, но совместимость следует проверить отдельно.
  • Токен в большинстве моделей передаётся в теле JSON полем token. JWT ли это, срок действия, refresh-token, logout/revoke, cookies, CORS и лимиты — неизвестно.
  • HTTP 401 обрабатывается в Windows как отказ авторизации; детали ниже.
  • Все времена событий/QSO в интеграции необходимо хранить и передавать в UTC, хотя точное требование сервера к часовому поясу и формату ts не подтверждено.

3. Реестр обнаруженных операций

Сокращения: S — проверено сервером; C — подтверждено клиентским кодом; U — URL/семантика частично не подтверждены.

Транспорт и метод

Адрес / путь

Назначение

Источник

Статус

HTTPS POST

/aiohttp/login

Авторизация, выдача токена, настроек

Android, Windows, Node-RED, HTTP

S

HTTPS POST

/aiohttp/location

Отправка координат / получение QTH-данных

Android, Node-RED

C

HTTPS POST

/aiohttp/location

Передача частоты станции

Android, Windows, Node-RED

C

HTTPS POST

/aiohttp/log

Отправка одного или нескольких QSO

Android, Windows, Node-RED

C

HTTPS POST

/aiohttp/log

Удаление QSO по серверному ts

Windows

C

HTTPS POST

/aiohttp/chat

Отправка сообщения чата

Android

C

HTTPS POST

/aiohttp/soundRecord

Отправка аудиозаписи, multipart

Windows

C

HTTPS GET

/static/js/defaultStationStatus.json

Публичная структура состояния станции

HTTP

S

HTTPS GET

/static/js/qth_now_locations.json

Данные QTHnow/локаций

HTTP

S

HTTPS GET

chat.json (относительно Retrofit baseUrl)

Чат станции

Android

U

HTTPS GET

static/js/talks.json (относительно Retrofit baseUrl)

Общие беседы

Android

U

HTTPS GET

/static/stations/{station}/{name}.json

Статические данные выбранной станции

Web JS

U

HTTPS GET

/aiohttp/station/{stationAdmin}/banlist

Список блокировок станции

Web JS

U

Веб-события

/exchange/active_stations

Изменения активных станций

Web JS

C

Веб-события

/exchange/active_users

Изменения активных пользователей

Web JS

C

Веб-события

/exchange/chat/talks

Подписка на общий чат

Web JS

C

Веб-события

/exchange/chat/{callsign}

Подписка на чат станции

Web JS

C

Веб-события

/exchange/pm/{callsign}

Личные сообщения

Web JS

C

Примечание. /exchange/... не следует запрашивать обычным GET как REST API: обычный GET /exchange/active_stations ранее отвечал 404. Точный транспорт подписки, handshake и авторизация требуют исследования. В интерфейсе также найдены административные действия над banlist; их точные методы и тела в полном виде не восстановлены.

4. POST /aiohttp/login

Назначение: получить токен и пользовательскую конфигурацию. Статус: HTTP + Android + Windows + Node-RED.

Запрос:

{
  "login": "<CALLSIGN>",
  "password": "<PASSWORD>"
}

Подтверждённые верхнеуровневые поля успешного ответа (из обезличенного HTTP-отчёта; значения скрыты):

{
  "token": "<TOKEN>",
  "callsign": "<CALLSIGN>",
  "settings": {},
  "chat_callsign": "<VALUE>",
  "email": "<VALUE>",
  "email_confirmed": false,
  "name": "<VALUE>",
  "pm_enabled": false,
  "verified": false,
  "banned_by": null,
  "gallery_quotas": {},
  "last_blog_entry_delete": null
}

Выше показана схема-пример ключей, а не буквальное тело ответа: конкретные типы отдельных полей, включая banned_by, gallery_quotas, last_blog_entry_delete, в обезличенном отчёте не зафиксированы. Значения false, null, {} — заполнители, не нормативные значения. В Android встречается вложенная settings.station.callsign; полнота settings неизвестна.

Обработка Windows: успешный HTTP-ответ приводит к десериализации LoginResponse, сохранению token и запуску таймера schedulePingTimer() (IL 36131–36188). При HTTP 401 токен очищается, приложение уведомляет об изменении состояния авторизации, и именно по этой ветке автоматическая попытка не планируется (IL 36255–36287). При не-401 ошибке может назначаться повторная авторизация, если активен флаг retry (IL 36289–36300); callback вызывает login(true) (IL 37095–37098). Точные интервалы и ограничения количества попыток неизвестны.

5. POST /aiohttp/location: координаты и частота

Назначение: обновление местоположения станции, получение сведений о QTH, RDA, RAFA, локаторе; либо передача текущей частоты. Статус: Android + Node-RED; ответ сервером в этой серии исследований не проверен.

Координаты:

{
  "token": "<TOKEN>",
  "location": [55.7525, 37.6300]
}

По Node-RED порядок элементов [latitude, longitude] (WGS84, десятичные градусы); DEX подтверждает тип Double[], но не геодезическую систему и порядок самостоятельно. Не путать с GeoJSON, где геометрия [longitude, latitude].

Частота:

{
  "token": "<TOKEN>",
  "freq": "14074.0"
}

freq — строка. В исследованной реализации Node-RED используется частота в кГц; Windows вызывает QSO.formatFreq(Decimal). Единицы и точность должны быть верифицированы для остальных реализаций.

Структура модели ответа Android (частичная, иллюстративная):

{
  "qth": {
    "fields": {
      "titles": ["RDA", "RAFA", "QTH field"],
      "values": ["<RDA>", null, null]
    },
    "loc": "<MAIDENHEAD>",
    "loc8": "<LOCATOR_SUFFIX>"
  }
}

Наличие qth, fields, titles, values, loc, loc8 подтверждено клиентской моделью, конкретные значения, порядок и обязательность — нет. Нужно учитывать несколько RDA около границ, пустой результат и ошибки определения. Данные о границах RDA в клиенте не найдены: наличие ответа /location не доказывает, что сервер TNXQSO предоставляет сами полигоны.

Независимый источник полигонов RDA (НЕ TNXQSO API)

Проверен отдельный сервис R1CF: https://map.r1cf.ru/geoserver/cite/wfs, OGC WFS. Успешно выполнены GetCapabilities, DescribeFeatureType и GetFeature слоя cite:RDA_2025X с OUTPUTFORMAT=application/json, получены MultiPolygon и атрибуты osm_id, name, rda, az_rating, name_en, modified. Выгруженный файл содержит 2 661 геообъект и 2 660 различных кодов RDA (из отдельного аудита). Этот внешний интерфейс не является маршрутом TNXQSO и описан здесь лишь как рекомендуемый независимый источник для офлайн-геопоиска RDA Logger / RDA LogFlow.

6. POST /aiohttp/log: отправка QSO

Статус: Android + Windows + Node-RED; HTTP-ответ от реального сервера на запись в рамках исследования не проверялся.

{
  "token": "<TOKEN>",
  "qso": [
    {
      "ts": "2026-10-17 10:00:00",
      "cs": "<OTHER_CALLSIGN>",
      "myCS": "<OWN_CALLSIGN>",
      "band": "7",
      "freq": "7010.5",
      "mode": "CW",
      "snt": "599",
      "rcv": "599",
      "loc": "<MAIDENHEAD>",
      "qth": ["<RDA>", "<RAFA>", "<QTH_FIELD>"],
      "no": "0"
    }
  ]
}

Все поля объекта QSO в Android-модели, кроме qth: String[], — строковые: band, cs, freq, loc, mode, myCS, no, rcv, snt, ts. В Android TNXLOG no при отправке явно устанавливается в строку "0" (DEX classes.dex около 0x445ada). Формат ts в примере следует клиентским образцам и не подтверждён как единственно разрешённый формат. Частота в показанном формате кГц — из клиентской реализации.

В Node-RED также встречаются comments, loc_rcv и другие поля; они не входят в перечисленную Android-модель и не объявляются обязательными. Windows сериализует собственный объект QSO; его полный публичный JSON-набор не был получен как сырой сетевой запрос.

Результат отправки и серверный идентификатор

Android Retrofit ожидает Call<List<QsoResult>>; у QsoResult есть поле ts: Float. В обработчике ответа Android сохраняет в локальную запись Qso.web_id = "OK" и обновляет Room/SQLite (MainActivity$5.onResponse, DEX около 0x43ec04–0x43ec08). Это не серверный идентификатор.

Windows TNXLOG ожидает NewQsoResponse[] с полем ts: Decimal? и сохраняет полученное значение в QSO.serverTs, используемое позднее для удаления. Если ts отсутствует, Windows присваивает serverTs=0 и другое состояние синхронизации. Следовательно, массив ответов и значение ts принципиальны для корректного ведения журнала. Связь между индексами запроса и ответа строится клиентом, но серверное обещание одинаковой длины и порядка ответов отдельно не проверено.

Иллюстрация ожидаемой формы (не захваченный ответ):

[
  {"ts": 1234567890.0}
]

Не установлено: значение ts при ошибках, предел batch, допустимый размер полей, поведение повторной загрузки одинаковой связи, идемпотентность, возможность upsert/редактирования уже опубликованного QSO, диапазон/точность идентификатора, случаи частичного успеха.

7. POST /aiohttp/log: удаление QSO

Статус: восстановлено из IL Windows TNXLOG; серверный вызов удаления не проводился.

Windows использует тот же endpoint /aiohttp/log, но передаёт отдельный тип QsoDeleteRequest : JSONToken с полем delete: Decimal. Конструктор берёт число из QSO.serverTs.

{
  "token": "<TOKEN>",
  "delete": 1234567890
}

delete — серверное значение ts, полученное при загрузке, а не локальный ID записи. Число условное. Это не HTTP DELETE и не подтверждённый путь /aiohttp/delete.

Критическая особенность клиента Windows: Tnxlog.deleteQso() сначала убирает запись из UI, затем из локальной LiteDB, затем вызывает сетевую операцию (IL 17512–17534). При потере связи возможна рассинхронизация; в собственной реализации рекомендуется хранить состояние pending_delete до подтверждения сервера.

Не установлено: тело ответа при успешном/ошибочном удалении, правила прав доступа, возможность повторного удаления, поведение отсутствующего serverTs, возврат удаления, ограничения на принадлежность QSO.

8. POST /aiohttp/chat

Статус: Android Retrofit; серверные ответы не проверены.

В Android присутствуют две модели:

Чат станции (ChatMessageData):

{
  "from": "<CALLSIGN>",
  "name": "<NAME>",
  "station": "<STATION>",
  "text": "<MESSAGE>",
  "token": "<TOKEN>"
}

Общий чат (ChatMessageDataTalks):

{
  "from": "<CALLSIGN>",
  "name": "<NAME>",
  "text": "<MESSAGE>"
}

Ожидается Call<String>. Отсутствие поля token во второй модели не доказывает отсутствие проверки авторизации на сервере: возможны cookie, другой контекст сессии или особенности конвертера.

Чтение: Android имеет относительные маршруты GET chat.json и GET static/js/talks.json, возвращающие List<ChatPost> с полями admin (Boolean), date, name, text, time, user. Полные абсолютные URL этих двух запросов не установлены из-за неизвестной настройки Retrofit baseUrl в данном месте.

9. POST /aiohttp/soundRecord

Статус: Windows IL, на сервере не проверено.

Тип отправки multipart/form-data. В _postSoundRecord формируется StreamContent(File.OpenRead(record)) и StringContent(config.token), оба включаются в MultipartFormDataContent. Предыдущий анализ несокращённого IL идентифицировал поля file (имя файла, тип audio/mp3) и token; в повторно предоставленном il_focused.txt строковые константы скрыты, поэтому данное уточнение основано на более раннем несокращённом извлечении. Путь вызывается как soundRecord относительно Windows base URL.

Успех клиентом оценивается по HttpResponseMessage.IsSuccessStatusCode (IL 33936–33944), тело ответа не интерпретируется. Ограничения на размер, формат аудио, права доступа, квоты и сопоставление с QSO — неизвестны.

10. Публичные JSON и веб-события

Проверенные ресурсы

GET /static/js/defaultStationStatus.json — HTTP 200, объект с ключами:

get, loc, online, rafa, rda, userFields, wff.

GET /static/js/qth_now_locations.json — HTTP 200, массив; на момент проверки 5 элементов. Содержание элементов без сырого ответа не следует считать документированным.

Не полностью подтверждённые URL

  • GET /static/stations/{station}/{name}.json — шаблон из Web JS, параметры и формат ответа неизвестны.
  • GET /aiohttp/station/{stationAdmin}/banlist — административный маршрут из Web JS; отдельные операции модификации banlist обнаружены в клиенте, но контракт не исследован.

События /exchange/...

Код веб-клиента использует каналы active_stations, active_users, chat/talks, chat/{callsign}, pm/{callsign} под /exchange/. Они не классифицируются как обычные REST GET. Протокол событий, направление сообщений, формат payload, аутентификация и механизм подписки пока не восстановлены.

11. Поведение Windows HTTP-клиента и очередей

Статус: IL-код Windows TNXLOG 0.4.16; это поведение клиентской программы, а не обязательное правило TNXQSO API.

  1. JSON сериализуется в текст, затем отправляется HttpClient.PostAsync (IL 33732–33747, 34076–34086).
  2. Тайм-аут HttpClient.Timeout = max(timeoutSeconds, 100) секунд (IL 34057–34070).
  3. При HTTP 2xx вызывается set_connected(true); при 401 — set_connected(false) и loginStateChanged (IL 34124–34153).
  4. Сетевая очередь logQueue — ConcurrentQueue<LogRequest>; вызывается TryPeek(). Если req.qso != null, выбирается _postQso(..., fromQueue=true), иначе _postDeleteQso(..., fromQueue=true) (IL 35312–35367).
  5. Извлечение из очереди (TryDequeue) выполняется только после true от отправки; при false элемент остаётся и цикл прерывается (IL 35400–35419).
  6. После очереди QSO запускается очередь аудиозаписей processSoundRecordsQueue() (IL 35421–35423).
  7. При успешном обычном запросе и подключённом состоянии обработка очереди может инициироваться вновь (fromQueue=false, IL 34179–34189).
  8. Логин может повторяться по таймеру (scheduleLoginRetryTimer); параметры таймера и гарантированное возобновление после перезапуска не установлены.

Не доказано: сохранение очередей между перезапусками, сколько и с каким интервалом отправляются повторы, гарантия ровно однократной доставки, восстановление данных после crash и дедупликация на стороне сервера.

12. Практические примеры безопасного чтения

Примеры не содержат действующих токенов. Нельзя выполнять POST записи или удаления с рабочим журналом ради проверки протокола.

# Схема публичной конфигурации, без авторизации
curl -fsS 'https://tnxqso.com/static/js/defaultStationStatus.json'

# Список QTHnow-локаций
curl -fsS 'https://tnxqso.com/static/js/qth_now_locations.json'

Авторизация (демонстрация с фиктивными значениями):

curl -sS -X POST 'https://tnxqso.com/aiohttp/login' \
  -H 'Content-Type: application/json' \
  --data '{"login":"<CALLSIGN>","password":"<PASSWORD>"}'

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

13. Обработка ошибок: что известно и что нет

Условие

Подтверждённое поведение клиента

Серверная гарантия

Успешный login HTTP 200

Токен и настройки разбираются

Проверено на тестовом аккаунте

HTTP 401 в Windows

Сброс токена/состояния авторизации

Формат тела ошибки неизвестен

HTTP не-2xx

Обработка отказа, возможный повтор логина

Классы кодов и схемы ошибок неизвестны

Timeout POST QSO

В клиенте может сохраниться элемент очереди

Неизвестно, записан ли QSO сервером

POST QSO 2xx

Клиент интерпретирует ответ ts

Частичный успех, формат ошибок не проверены

POST delete 2xx

Клиент считает операцию успешной

Семантика при повторе не проверена

Загрузка аудио 2xx

Windows считает успешной по HTTP

Тело ответа/идентификатор неизвестны

Неизвестны: официальные 400/403/404/409/422/429/5xx тела, rate limits, pagination для журналов, серверный поиск QSO, критерий дубликата, корреляция запрос/ответ, срок токена.

14. Рекомендации к интеграции RDA Logger / RDA LogFlow

  • Локальный журнал — главный источник истины; синхронизация TNXQSO является отдельным состоянием, а не заменой локальных QSO.
  • Для каждого QSO хранить local_id, server_ts (точный decimal/string без потери точности), sync_state, last_attempt_utc, last_error, хеш/снимок переданных полей.
  • Для удаления использовать только подтверждённый server_ts, сначала pending_delete, затем подтверждённое удаление.
  • При тайм-ауте POST и неизвестном результате использовать состояние uncertain, а не безусловный автоматический повтор, пока не будет верифицирован механизм сверки/дедупликации.
  • Пакеты ограничивать консервативно и сохранять порядок операций до выявления настоящего ограничения API.
  • Никогда не выводить token/password в диагностических логах, отчётах и пользовательских ZIP.
  • RDA-определение по координатам может выполняться локально по отдельной базе R1CF, но это не заменяет проверки поведения /aiohttp/location TNXQSO и не делает R1CF частью TNXQSO API.

15. Реестр пробелов для полного исследования серверного API

Высший приоритет

  1. Захват и обезличивание точных успешных/ошибочных HTTP-ответов /aiohttp/location, /aiohttp/log, /aiohttp/chat, /aiohttp/soundRecord на специально выделенном тестовом аккаунте.
  2. Проверка идемпотентности POST /aiohttp/log, повторной передачи QSO и соответствия массива ответов входному массиву; без риска для существующего журнала.
  3. Выяснение механизмов получения опубликованных QSO, исправления записи, восстановления после потери ответа, статуса синхронизации и пагинации.
  4. Полный маршрут и протокол GET чтения чатов и /exchange/... (XHR/WebSocket/SSE, параметры и payload).
  5. Типы значений и обязательность полей успешного login и qth-ответа, включая множественные RDA.

Следующий приоритет

  1. Остальные JavaScript-ресурсы и операции публичного веб-клиента: пользователи, станции, приватные сообщения, статусы, модерация, геолокация, фото и прочие медиа.
  2. Официальные ограничения, токены, перезапуск сессии и rate limit, версии API.
  3. Получение и статическая проверка клиентов QTHnow, TNXPost, TNXInfo; поиск недоступных TNXLOG методов в них.
  4. Отдельная проверка использования и лицензирования данных TNXQSO и R1CF.

16. Привязка к исходным свидетельствам

Раздел

Источник / технические маркеры

Авторизация

Android NetworkInterface.login, Windows <login>d__43::MoveNext IL 35999–36344; tnxqso_api_check.json

Геолокация

Android sendLocation, sendFrequency; экспорт Node-RED location, Set freq body

Создание QSO

Android sendQso; DEX MainActivity.sendQsoToServer; Windows <_postQso>d__29 IL 34295–34523

no="0", web_id="OK"

Android DEX смещения около 0x445ada, 0x43ec04

Удаление

Windows <_postDeleteQso>d__30 IL 34556–34664; QsoDeleteRequest, QSO.serverTs

Очередь

Windows <processQueue>d__40 IL 35282–35479

Локальное удаление до сети

Windows Tnxlog/<deleteQso>d__33 IL 17512–17534

Транспорт

Windows <post>d__26 IL 33707–33806; <post>d__28 IL 34006–34261

Аудио

Windows <_postSoundRecord>d__27 IL 33838–33968

Чаты

Android Retrofit sendMessage, getStationChat, getTalks; Web JS /exchange/...

Публичные JSON

tnxqso_api_check.json с кодом 200; js_contexts.json

Внешние RDA-полигоны

Отдельный GeoServer R1CF; RDA_2025X.geojson, отчёт аудита

17. Заключение

На данный момент достоверно реконструирован рабочий каркас интеграции: авторизация, обновление геопозиции и частоты, загрузка QSO, удаление по serverTs, отправка чата и аудио, часть публичных JSON и веб-событий. Не установлена полнота закрытого API сервера. Документ пригоден для проектирования клиента с явной обработкой неподтверждённых сценариев; его нельзя использовать как гарантию, что неизвестные методы отсутствуют.