record-daemon: fix event subscribe

This commit is contained in:
2026-03-19 21:38:40 +01:00
parent 2ddb953321
commit 7e346a33e0
2 changed files with 152 additions and 25 deletions

View File

@@ -68,8 +68,11 @@ impl rustls::client::danger::ServerCertVerifier for InsecureVerifier {
/// LQP WebSocket endpoints to subscribe to.
const SUBSCRIBE_ENDPOINTS: &[&str] = &[
"/lol-gameflow/v1/gameflow-phase",
"/lol-gameflow/v1/session",
"/lol-matchmaking/v1/ready-check",
"/lol-game-events/v1/game-events",
"/lol-champ-select/v1/session",
"/lol-lobby/v2/lobby",
];
/// LQP REST API endpoints.
@@ -274,16 +277,18 @@ impl LqpClient {
let (mut write, mut read) = ws_stream.split();
// Subscribe to endpoints
// Subscribe to endpoints using OnJsonApiEvent format
// Format: [5, "OnJsonApiEvent", endpoint]
for endpoint in SUBSCRIBE_ENDPOINTS {
let subscribe_msg = serde_json::json!([5, endpoint]);
let subscribe_msg = serde_json::json!([5, "OnJsonApiEvent", endpoint]);
let msg = Message::Text(subscribe_msg.to_string());
info!("Subscribing to: {} with OnJsonApiEvent", endpoint);
write
.send(msg)
.await
.map_err(|e| LqpError::WebSocketError(e.to_string()))?;
trace!("Subscribed to {}", endpoint);
}
info!("All subscriptions sent");
// Clone references for the async block
let event_sender = self.event_sender.clone();
@@ -301,6 +306,11 @@ impl LqpClient {
match msg {
Ok(Message::Text(text)) => {
debug!("Received text message: {} bytes", text.len());
if text.is_empty() {
debug!("Empty text message received, skipping");
continue;
}
if let Some(event) = Self::parse_websocket_message(&text) {
// Update state based on event
Self::update_state_from_event(&state, &event).await;
@@ -311,19 +321,42 @@ impl LqpClient {
}
}
}
Ok(Message::Binary(data)) => {
debug!("Received binary message: {} bytes", data.len());
// Try to parse as UTF-8
if let Ok(text) = String::from_utf8(data) {
if !text.is_empty() {
if let Some(event) = Self::parse_websocket_message(&text) {
// Update state based on event
Self::update_state_from_event(&state, &event).await;
// Broadcast event
if event_sender.send(event.clone()).is_err() {
trace!("No event subscribers");
}
}
}
}
}
Ok(Message::Close(_)) => {
info!("WebSocket closed by server");
break;
}
Ok(Message::Ping(data)) => {
// Respond with pong
debug!("Received ping, sending pong");
let _ = write.send(Message::Pong(data)).await;
}
Ok(Message::Pong(_)) => {
debug!("Received pong");
}
Ok(Message::Frame(_)) => {
debug!("Received raw frame");
}
Err(e) => {
error!("WebSocket error: {}", e);
break;
}
_ => {}
}
}
@@ -337,48 +370,135 @@ impl LqpClient {
/// Parse a WebSocket message into a game event.
fn parse_websocket_message(text: &str) -> Option<GameEvent> {
trace!("WebSocket message: {}", text);
debug!("WebSocket message: {}", text);
// Parse the message array format: [type, endpoint, data]
let value: serde_json::Value = serde_json::from_str(text).ok()?;
// Parse the message array format: [type, callback, data]
let value: serde_json::Value = match serde_json::from_str(text) {
Ok(v) => v,
Err(e) => {
warn!("Failed to parse WebSocket message as JSON: {}", e);
return None;
}
};
// Check if it's an event message (type 8)
if let Some(arr) = value.as_array() {
debug!("Message is array with {} elements", arr.len());
if arr.len() >= 3 {
let msg_type = arr.first()?.as_u64()?;
debug!("Message type: {}", msg_type);
if msg_type == 8 {
// Event message
let endpoint = arr.get(1)?.as_str()?;
let data = arr.get(2)?;
// Event message format: [8, "OnJsonApiEvent", {"data": ..., "eventType": ..., "uri": ...}]
let callback = arr.get(1)?.as_str()?;
let event_data = arr.get(2)?;
return Self::parse_event_from_endpoint(endpoint, data);
if callback == "OnJsonApiEvent" {
// Extract the actual URI and data from the event
let uri = event_data.get("uri")?.as_str()?;
let data = event_data.get("data")?;
let event_type = event_data.get("eventType").and_then(|t| t.as_str()).unwrap_or("Update");
debug!("OnJsonApiEvent: uri={}, eventType={}, data={:?}", uri, event_type, data);
return Self::parse_event_from_uri(uri, event_type, data);
} else {
debug!("Unknown callback: {}", callback);
}
} else if msg_type == 4 {
// Response to subscription - this is normal
debug!("Subscription response received");
} else if msg_type == 0 {
// Welcome message
info!("WebSocket welcome message received");
}
}
} else {
debug!("Message is not an array: {:?}", value);
}
None
}
/// Parse an event based on the endpoint.
fn parse_event_from_endpoint(endpoint: &str, data: &serde_json::Value) -> Option<GameEvent> {
match endpoint {
"/lol-gameflow/v1/gameflow-phase" => {
let phase = data.as_str()?;
Some(
/// Parse an event based on the URI.
fn parse_event_from_uri(uri: &str, event_type: &str, data: &serde_json::Value) -> Option<GameEvent> {
info!("Parsing event from URI: {} (type: {})", uri, event_type);
// Handle gameflow phase changes
if uri == "/lol-gameflow/v1/gameflow-phase" {
let phase = data.as_str()?;
info!("Gameflow phase changed to: {}", phase);
// Update internal state based on phase
return Some(
GameEvent::from_json(&serde_json::json!({
"eventType": "lcu-phase-change",
"phase": phase
}))
.unwrap_or(GameEvent::Unknown),
);
}
// Handle gameflow session updates
if uri == "/lol-gameflow/v1/session" {
if let Some(phase) = data.get("phase").and_then(|p| p.as_str()) {
info!("Gameflow session phase: {}", phase);
// Check for game start
if phase == "InProgress" {
info!("Game is now in progress!");
// Extract game info
let game_id = data.get("gameData")
.and_then(|gd| gd.get("gameId"))
.and_then(|id| id.as_u64())
.unwrap_or(0);
return Some(
GameEvent::from_json(&serde_json::json!({
"eventType": "lcu-game-start",
"gameId": game_id
}))
.unwrap_or(GameEvent::Unknown),
);
}
return Some(
GameEvent::from_json(&serde_json::json!({
"eventType": "lcu-phase-change",
"phase": phase
}))
.unwrap_or(GameEvent::Unknown),
)
}
"/lol-game-events/v1/game-events" => GameEvent::from_json(data),
_ => {
trace!("Unhandled endpoint: {}", endpoint);
None
);
}
}
// Handle game events (kills, deaths, objectives)
if uri == "/lol-game-events/v1/game-events" {
info!("Game event received: {:?}", data);
return GameEvent::from_json(data);
}
// Handle ready check
if uri == "/lol-matchmaking/v1/ready-check" {
info!("Ready check event: {:?}", data);
return None;
}
// Handle champion select
if uri == "/lol-champ-select/v1/session" {
info!("Champion select event: {:?}", data);
return None;
}
// Handle lobby
if uri.starts_with("/lol-lobby") {
debug!("Lobby event: {}", uri);
return None;
}
debug!("Unhandled URI: {}", uri);
None
}
/// Update internal state from a game event.

View File

@@ -276,7 +276,14 @@ pub enum EventData {
impl GameEvent {
/// Parse a game event from raw WebSocket data.
pub fn from_json(value: &serde_json::Value) -> Option<Self> {
serde_json::from_value(value.clone()).ok()
match serde_json::from_value(value.clone()) {
Ok(event) => Some(event),
Err(e) => {
// Log the parsing error for debugging
tracing::warn!("Failed to parse game event: {}. Data: {:?}", e, value);
None
}
}
}
/// Check if this event is relevant for recording.