record-daemon: refactor/remove pregame metadata in daemon
All checks were successful
record-daemon / Build, check and test (push) Successful in 2m0s
All checks were successful
record-daemon / Build, check and test (push) Successful in 2m0s
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user