The public Discord gateway rejects OAuth2 access tokens (close 4004),
so v1 could never set presence: switch config to a raw user token and
delete the oauth/http module.
- handle close frames; exit fatal on 4004 instead of looping forever
- fix reversed IPv4 packing in sceNetConnect (wrong host)
- fix SSL_read treating WANT_READ/no-data as disconnect
- keep frame bytes glued to the 101 handshake response (HELLO)
- grow recv buffer for large READY payloads; drain-and-skip >2MB
- mask all client frames incl. control; overflow-safe length checks
- drop RESUME (was dead: connect wiped session_id); fresh IDENTIFY
- clear presence stays online, add elapsed timestamps
- tick gateway every second so heartbeats never land late
- connect backoff 5s..300s; live config reload each cycle
Real desktop clients send the gateway capabilities bitfield and an
initial presence inside IDENTIFY, and native clients do not send an
Origin header on the WebSocket upgrade. Match all three for a closer
desktop-client profile.
- identify properties now present a Windows Discord desktop fingerprint
(os windows, browser 'Discord Client', Electron UA) rather than
advertising os:'PS4', browser:'orbisRPC'
- HTTP user-agent (API calls + WS upgrade) now a desktop-client UA
instead of 'orbisRPC/1.0'
ws: SSL_connect on the non-blocking socket now polls WANT_READ/WANT_WRITE
with a deadline (previously it would fail the very first handshake);
handshake response read in chunks so the 5s deadline can't spuriously
trip; ws_send_text errors on oversized frames instead of silently
truncating; added ws_send_pong for RFC6455 server pings.
discord: heartbeat ack tracking -> reconnect on 2x-interval silence;
handle op 7 (reconnect) and op 9 (invalid session, clears session_id);
parse the sequence from the correct "s" field (was "seq", never
matched, heartbeat always sent d:0); READY captures session_id for
RESUME; reconnect now RESUMEs (op 6) when a session exists and falls
back to IDENTIFY on invalid-session; party size now emitted in the
activity; dropped the unused 'intent' arg; zeroed gateway-fetch buffer.
main: use config presence_state (was hardcoded); reset last_name after
reconnect so the current game is re-pushed immediately.
cfg: clamp poll_interval_s to [5,300]; atomic config save (tmp+rename).
log: seconds in timestamps; rotate log at 512 KB.
jsonlite: jl_obj_get/jl_arr_at return non-const (fixes const-drop
warnings); stringify emits proper "null" for missing pair values.
detect: trim whitespace around app.xml title; drop per-poll log spam.