Files
Qlyra/ws2_dump.py
T
klockky cf154b006b feat: звонки 1:1 (аудио) — WebRTC + ws2-сигналинг
Реализованы голосовые звонки 1:1 поверх инфраструктуры VK/OK
(flutter_webrtc + сигналинг ws2 wss://videowebrtc.okcdn.ru). Протокол
восстановлен реверсом захваченных пакетов и декомпиляцией
ru.ok.android.externcalls.sdk.

Ядро (lib/core/calls/):
- conversation_params.dart — декодер vcp (LZ4-блок), с unit-тестом
- ws2_signaling.dart — клиент сигналинга ws2 (transmit-data, ICE,
  accept-call, hangup, change-media-settings, ping/pong)
- call_session.dart — WebRTC-контроллер для звонящего и вызываемого
- call_controller.dart — глобальный оркестратор: пуш opcode 137,
  исходящие, приём/отклонение, управление активной сессией

Бэкенд:
- CallsModule.initiateCall — инициация исходящего (opcode 78)
- общий LZ4-block декодер вынесен в core/protocol/lz4_block.dart
  (используется и транспортом, и vcp)

UI / обвязка:
- CallScreen переписан под живой CallSession (+ кнопка «свернуть»
  без завершения звонка, возврат повторным нажатием 📞)
- кнопка звонка в шапке диалога
- инициализация CallController на логине, показ входящего по пушу
- дев-тумблер: принудительный сигнал состояния микрофона без
  изменения реального аудиотрека

Прочее:
- зависимость flutter_webrtc ^0.12.5
- Android: RECORD_AUDIO / MODIFY_AUDIO_SETTINGS / BLUETOOTH
2026-06-10 18:21:27 +03:00

73 lines
2.1 KiB
Python

"""mitmproxy addon: dump okcdn call signaling (ws2 WebSocket + HTTP) to a log."""
import json
import time
from mitmproxy import http, ctx
LOG = r"C:\Users\klockky\Komet\docs\ws2_capture.log"
HOSTS = ("okcdn.ru", "videowebrtc")
def _interesting(host: str) -> bool:
return any(h in host for h in HOSTS)
def _w(line: str) -> None:
with open(LOG, "a", encoding="utf-8") as f:
f.write(line + "\n")
def _fmt(content: bytes) -> str:
try:
text = content.decode("utf-8")
try:
return json.dumps(json.loads(text), ensure_ascii=False, indent=2)
except Exception:
return text
except Exception:
return "HEX " + content.hex()
def websocket_start(flow: http.HTTPFlow) -> None:
if not _interesting(flow.request.pretty_host):
return
_w("=" * 70)
_w(f"# WS OPEN {flow.request.pretty_host} {flow.request.path}")
_w(f" headers: {dict(flow.request.headers)}")
_w("=" * 70)
def websocket_message(flow: http.HTTPFlow) -> None:
if not _interesting(flow.request.pretty_host):
return
msg = flow.websocket.messages[-1]
arrow = "TX (client->server)" if msg.from_client else "RX (server->client)"
ts = time.strftime("%H:%M:%S")
_w(f"\n--- {arrow} {ts} {len(msg.content)} B host={flow.request.pretty_host} ---")
_w(_fmt(msg.content))
def websocket_end(flow: http.HTTPFlow) -> None:
if not _interesting(flow.request.pretty_host):
return
_w(f"\n# WS CLOSE {flow.request.pretty_host}\n")
def response(flow: http.HTTPFlow) -> None:
host = flow.request.pretty_host
if not _interesting(host):
return
if flow.websocket is not None:
return
_w("\n" + "#" * 70)
_w(f"# HTTP {flow.request.method} {host}{flow.request.path} -> {flow.response.status_code}")
if flow.request.content:
_w(" REQ: " + _fmt(flow.request.content)[:2000])
if flow.response.content:
_w(" RES: " + _fmt(flow.response.content)[:2000])
def load(loader) -> None:
_w(f"\n\n########## capture session start {time.strftime('%Y-%m-%d %H:%M:%S')} ##########")
ctx.log.info("ws2_dump addon loaded")