record-daemon: refactor/remove pregame metadata in daemon
All checks were successful
record-daemon / Build, check and test (push) Successful in 2m0s

This commit is contained in:
2026-03-27 16:09:04 +01:00
parent 52f8be7caa
commit 785e5073ec
4 changed files with 5 additions and 405 deletions

View File

@@ -209,151 +209,6 @@ pub struct ChampionSelectPlayer {
pub skin_id: Option<u64>,
}
// =============================================================================
// Rune Pages API Responses
// =============================================================================
/// Response from `/lol-perks/v1/currentpage`
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RunePageResponse {
/// Rune page ID.
#[serde(default)]
pub id: Option<u64>,
/// Rune page name.
#[serde(default)]
pub name: Option<String>,
/// Whether this is the current page.
#[serde(default)]
pub current: Option<bool>,
/// Primary style ID.
#[serde(default)]
pub primary_style_id: Option<u64>,
/// Secondary style ID.
#[serde(default)]
pub sub_style_id: Option<u64>,
/// Selected perk IDs.
#[serde(default)]
pub selected_perk_ids: Option<Vec<u64>>,
}
// =============================================================================
// End of Game Stats API Responses
// =============================================================================
/// Response from `/lol-end-of-game/v1/eog-stats-block`
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EndOfGameStatsResponse {
/// Game ID.
#[serde(default)]
pub game_id: Option<u64>,
/// Game length in seconds.
#[serde(default)]
pub game_length: Option<f64>,
/// Match ID.
#[serde(default)]
pub match_id: Option<u64>,
/// Game result.
#[serde(default)]
pub game_result: Option<String>,
/// Local player data.
#[serde(default)]
pub local_player: Option<EndOfGamePlayer>,
/// Teams data.
#[serde(default)]
pub teams: Option<Vec<EndOfGameTeam>>,
/// Players (legacy format).
#[serde(default)]
pub players: Option<Vec<EndOfGamePlayer>>,
}
/// Player in end-of-game stats.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EndOfGamePlayer {
/// Whether this is the local player.
#[serde(default)]
pub is_local_player: Option<bool>,
/// Player stats.
#[serde(default)]
pub stats: Option<PlayerStats>,
/// Items.
#[serde(default)]
pub items: Option<Vec<u64>>,
}
/// Team in end-of-game stats.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EndOfGameTeam {
/// Whether this is the player's team.
#[serde(default)]
pub is_player_team: Option<bool>,
/// Whether this is the winning team.
#[serde(default)]
pub is_winning_team: Option<bool>,
/// Players on the team.
#[serde(default)]
pub players: Option<Vec<EndOfGamePlayer>>,
}
/// Player stats in end-of-game.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub struct PlayerStats {
/// Kills.
#[serde(default)]
pub champions_killed: Option<u64>,
/// Deaths.
#[serde(default)]
pub num_deaths: Option<u64>,
/// Assists.
#[serde(default)]
pub assists: Option<u64>,
/// Minions killed (CS).
#[serde(default)]
pub minions_killed: Option<u64>,
/// Gold earned.
#[serde(default)]
pub gold_earned: Option<u64>,
/// Total damage dealt to champions.
#[serde(default)]
pub total_damage_dealt_to_champions: Option<u64>,
/// Total damage taken.
#[serde(default)]
pub total_damage_taken: Option<u64>,
/// Vision score.
#[serde(default)]
pub vision_score: Option<f64>,
/// Win status (1 = win).
#[serde(default)]
pub win: Option<u64>,
}
// =============================================================================
// Live Client Data API Responses
// =============================================================================
@@ -472,59 +327,6 @@ pub struct LiveClientItem {
// Helper implementations
// =============================================================================
impl EndOfGameStatsResponse {
/// Get the local player from the response.
pub fn get_local_player(&self) -> Option<&EndOfGamePlayer> {
// First check local_player field
if let Some(ref player) = self.local_player {
return Some(player);
}
// Then check teams
if let Some(ref teams) = self.teams {
for team in teams {
if let Some(ref players) = team.players {
for player in players {
if player.is_local_player == Some(true) {
return Some(player);
}
}
}
}
}
// Finally check legacy players array
if let Some(ref players) = self.players {
return players.first();
}
None
}
/// Check if the local player won.
pub fn is_victory(&self) -> bool {
// Check local player stats
if let Some(player) = self.get_local_player() {
if let Some(ref stats) = player.stats {
if stats.win == Some(1) {
return true;
}
}
}
// Check teams
if let Some(ref teams) = self.teams {
for team in teams {
if team.is_player_team == Some(true) && team.is_winning_team == Some(true) {
return true;
}
}
}
false
}
}
impl ChampionSelectResponse {
/// Get the local player's champion selection.
pub fn get_local_player_selection(&self) -> Option<&ChampionSelectPlayer> {
@@ -552,80 +354,6 @@ impl ChampionSelectResponse {
}
}
// =============================================================================
// Aggregated Data Containers
// =============================================================================
/// Container for pre-game data collected from multiple API calls.
/// Stores raw API responses directly for maximum flexibility.
#[derive(Debug, Clone, Default)]
pub struct PreGameData {
/// Session data (map, game mode, queue info).
pub session: Option<GameflowSessionResponse>,
/// Summoner data (puuid, name).
pub summoner: Option<SummonerResponse>,
/// Champion select data (champion, skin, team).
pub champion_select: Option<ChampionSelectResponse>,
/// Rune page data.
pub rune_page: Option<RunePageResponse>,
}
impl PreGameData {
/// Get the summoner name from any available source.
pub fn summoner_name(&self) -> Option<&str> {
self.summoner
.as_ref()
.and_then(|s| s.display_name.as_deref())
.or_else(|| self.summoner.as_ref().and_then(|s| s.name.as_deref()))
}
/// Get the PUUID.
pub fn puuid(&self) -> Option<&str> {
self.summoner.as_ref()?.puuid.as_deref()
}
/// Get the champion ID from champion select.
pub fn champion_id(&self) -> Option<u64> {
let cs = self.champion_select.as_ref()?;
let player = cs.get_local_player_selection()?;
player.champion_id
}
/// Get the team ID from champion select.
pub fn team(&self) -> Option<i64> {
let cs = self.champion_select.as_ref()?;
let player = cs.get_local_player_selection()?;
player.team
}
/// Get the skin ID from champion select.
pub fn skin_id(&self) -> Option<u64> {
let cs = self.champion_select.as_ref()?;
let player = cs.get_local_player_selection()?;
player.skin_id
}
/// Get the map name from session.
pub fn map_name(&self) -> Option<&str> {
self.session.as_ref()?.map.as_deref()
}
/// Get the game mode from session.
pub fn game_mode(&self) -> Option<&str> {
self.session.as_ref()?.game_mode.as_deref()
}
/// Get the queue ID from session.
pub fn queue_id(&self) -> Option<u64> {
self.session.as_ref()?.queue_id
}
/// Get the rune page name.
pub fn rune_page_name(&self) -> Option<&str> {
self.rune_page.as_ref()?.name.as_deref()
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -638,13 +366,4 @@ mod tests {
assert_eq!(summoner.puuid, Some("abc-123".to_string()));
assert_eq!(summoner.display_name, Some("TestPlayer".to_string()));
}
#[test]
fn test_player_stats_deserialization() {
let json = r#"{"CHAMPIONS_KILLED": 10, "NUM_DEATHS": 3, "ASSISTS": 15}"#;
let stats: PlayerStats = serde_json::from_str(json).unwrap();
assert_eq!(stats.champions_killed, Some(10));
assert_eq!(stats.num_deaths, Some(3));
assert_eq!(stats.assists, Some(15));
}
}

View File

@@ -11,8 +11,8 @@ use tokio_tungstenite::{connect_async_tls_with_config, tungstenite::protocol::Me
use tracing::{debug, error, info, trace, warn};
use super::api_types::{
ActivePlayerResponse, ChampionSelectResponse, EndOfGameStatsResponse, GameflowSessionResponse,
PlayerListResponse, PreGameData, RunePageResponse, SummonerResponse,
ActivePlayerResponse, ChampionSelectResponse, GameflowSessionResponse, PlayerListResponse,
SummonerResponse,
};
use super::auth::LockfileCredentials;
use super::endpoints;
@@ -394,11 +394,6 @@ impl LqpClient {
self.request("GET", endpoints::CHAMPION_SELECT).await
}
/// Get end-of-game stats (typed).
pub async fn get_game_stats_typed(&self) -> Result<EndOfGameStatsResponse> {
self.request_typed("GET", endpoints::GAME_STATS).await
}
/// Get end-of-game stats (raw JSON for backward compatibility).
pub async fn get_game_stats(&self) -> Result<serde_json::Value> {
self.request("GET", endpoints::GAME_STATS).await
@@ -409,11 +404,6 @@ impl LqpClient {
self.request("GET", endpoints::CHAMPION_SUMMARY).await
}
/// Get current rune page (typed).
pub async fn get_rune_page_typed(&self) -> Result<RunePageResponse> {
self.request_typed("GET", endpoints::RUNE_PAGES).await
}
/// Get current rune page (raw JSON for backward compatibility).
pub async fn get_rune_page(&self) -> Result<serde_json::Value> {
self.request("GET", endpoints::RUNE_PAGES).await
@@ -497,49 +487,6 @@ impl LqpClient {
pub async fn fetch_raw_end_game_stats(&self) -> Result<serde_json::Value> {
self.request("GET", endpoints::GAME_STATS).await
}
/// Fetch pre-game data (stores raw API responses directly).
pub async fn fetch_pregame_data(&self) -> Result<PreGameData> {
let mut data = PreGameData::default();
// Fetch all API responses in parallel where possible
let (session, summoner, champ_select, rune_page) = tokio::join!(
self.get_session_typed(),
self.get_summoner_typed(),
self.get_champion_select_typed(),
self.get_rune_page_typed()
);
// Store session data
if let Ok(session) = session {
data.session = Some(session);
}
// Store summoner data and update state
if let Ok(summoner) = summoner {
if let Some(ref puuid) = summoner.puuid {
self.state.write().await.local_puuid = Some(puuid.clone());
}
data.summoner = Some(summoner);
}
// Store champion select data
if let Ok(champ_select) = champ_select {
data.champion_select = Some(champ_select);
}
// Store rune page data
if let Ok(rune_page) = rune_page {
data.rune_page = Some(rune_page);
}
Ok(data)
}
/// Fetch end-of-game stats (returns raw API response).
pub async fn fetch_game_end_stats(&self) -> Result<EndOfGameStatsResponse> {
self.get_game_stats_typed().await
}
}
impl Default for LqpClient {

View File

@@ -13,9 +13,8 @@ mod tls;
mod websocket;
pub use api_types::{
ActivePlayerResponse, ChampionSelectPlayer, ChampionSelectResponse, EndOfGamePlayer,
EndOfGameStatsResponse, EndOfGameTeam, GameData, GameflowSessionResponse, LiveClientItem,
LiveClientPlayer, PlayerListResponse, PlayerStats, PreGameData, QueueData, RunePageResponse,
ActivePlayerResponse, ChampionSelectPlayer, ChampionSelectResponse, GameData,
GameflowSessionResponse, LiveClientItem, LiveClientPlayer, PlayerListResponse, QueueData,
SummonerResponse, SummonerSpellsData, TeamPlayer, Timers,
};
pub use auth::{LockfileCredentials, LockfileWatcher};

View File

@@ -56,8 +56,6 @@ struct Daemon {
event_mapper: Arc<RwLock<EventMapper>>,
/// Current recording ID (if recording).
current_recording_id: Arc<RwLock<Option<uuid::Uuid>>>,
/// Pre-game data (collected before game starts).
pregame_data: Arc<RwLock<Option<record_daemon::lqp::PreGameData>>>,
/// IPC server.
ipc_server: Option<IpcServer>,
/// Shutdown signal.
@@ -77,7 +75,6 @@ impl Daemon {
timeline_store: Arc::new(RwLock::new(TimelineStore::new())),
event_mapper: Arc::new(RwLock::new(EventMapper::new())),
current_recording_id: Arc::new(RwLock::new(None)),
pregame_data: Arc::new(RwLock::new(None)),
ipc_server: None,
shutdown_tx,
}
@@ -232,78 +229,19 @@ impl Daemon {
// Handle pre-game data collection
match &event {
GameEvent::PhaseChange(info) if info.phase == "ChampSelect" => {
info!("[EVENT_HANDLER] Champion select started, fetching pre-game data");
match self.lqp_client.fetch_pregame_data().await {
Ok(data) => {
info!("[EVENT_HANDLER] Pre-game data fetched: {:?}", data);
*self.pregame_data.write() = Some(data);
}
Err(e) => {
warn!("[EVENT_HANDLER] Failed to fetch pre-game data: {}", e);
}
}
info!("[EVENT_HANDLER] Champion select started");
}
GameEvent::ChampionPick(pick) if pick.is_local_player => {
info!(
"[EVENT_HANDLER] Local player picked champion: {}",
pick.champion_name
);
// Champion pick info is now stored in champion_select response
// No need to manually update - refetch if needed
}
GameEvent::GameStart(info) => {
info!(
"[EVENT_HANDLER] Game started with metadata: queue={:?}, mode={:?}, map={:?}",
info.queue_type, info.game_mode, info.map_name
);
// Update pre-game data with game start info if needed
let mut pregame = self.pregame_data.write();
// Extract player-specific data from session using puuid
let (_champion_id, _team, summoner_name) = if let Some(ref session) = info.session {
// Get puuid from pregame data (fetched earlier)
let puuid = pregame.as_ref().and_then(|d| d.puuid());
if let Some(puuid) = puuid {
// Use the puuid to find the correct player's data
let champ_id = session.get_champion_id(puuid);
let team_id = session.get_team(puuid);
let summoner = session.get_summoner_name(puuid).map(|s| s.to_string());
info!("[EVENT_HANDLER] Found player data via puuid: champion_id={:?}, team={:?}, summoner={:?}",
champ_id, team_id, summoner);
(champ_id, team_id, summoner)
} else {
// No puuid available, can't determine player
warn!("[EVENT_HANDLER] No puuid available, cannot determine player-specific data");
(None, None, None)
}
} else {
(None, None, None)
};
// If we don't have pre-game data, create minimal from game start info
if pregame.is_none() {
use record_daemon::lqp::{GameflowSessionResponse, PreGameData};
*pregame = Some(PreGameData {
session: Some(GameflowSessionResponse {
map: info.map_name.clone(),
game_mode: info.game_mode.clone(),
queue_id: info.queue_id.map(|id| id as u64),
..Default::default()
}),
summoner: summoner_name.map(|name| {
use record_daemon::lqp::SummonerResponse;
SummonerResponse {
display_name: Some(name),
..Default::default()
}
}),
..Default::default()
});
}
}
_ => {}
}
@@ -434,9 +372,6 @@ impl Daemon {
// Don't propagate error - keep daemon running
}
// Clear pre-game data
*self.pregame_data.write() = None;
}
_ => {}
}