record-daemon: fmt, clippy
All checks were successful
record-daemon / Build, check and test (push) Successful in 2m12s

This commit is contained in:
2026-03-25 11:07:36 +01:00
parent 12fe579aca
commit f81eff8496
5 changed files with 189 additions and 78 deletions

View File

@@ -976,17 +976,20 @@ impl LqpClient {
/// Get active player data from live client. /// Get active player data from live client.
pub async fn get_live_client_active_player(&self) -> Result<serde_json::Value> { pub async fn get_live_client_active_player(&self) -> Result<serde_json::Value> {
self.request("GET", endpoints::LIVE_CLIENT_DATA_ACTIVE_PLAYER).await self.request("GET", endpoints::LIVE_CLIENT_DATA_ACTIVE_PLAYER)
.await
} }
/// Get player list from live client (contains all players with puuid and summoner names). /// Get player list from live client (contains all players with puuid and summoner names).
pub async fn get_live_client_player_list(&self) -> Result<serde_json::Value> { pub async fn get_live_client_player_list(&self) -> Result<serde_json::Value> {
self.request("GET", endpoints::LIVE_CLIENT_DATA_PLAYER_LIST).await self.request("GET", endpoints::LIVE_CLIENT_DATA_PLAYER_LIST)
.await
} }
/// Get local player selection from champion select. /// Get local player selection from champion select.
pub async fn get_local_player_selection(&self) -> Result<serde_json::Value> { pub async fn get_local_player_selection(&self) -> Result<serde_json::Value> {
self.request("GET", endpoints::CHAMPION_SELECT_LOCAL_PLAYER).await self.request("GET", endpoints::CHAMPION_SELECT_LOCAL_PLAYER)
.await
} }
/// Fetch pre-game metadata (champion, skin, runes, queue info). /// Fetch pre-game metadata (champion, skin, runes, queue info).
@@ -1130,9 +1133,13 @@ impl LqpClient {
// Get summoner info - try multiple field names for summoner name // Get summoner info - try multiple field names for summoner name
if let Ok(summoner) = self.get_summoner().await { if let Ok(summoner) = self.get_summoner().await {
metadata.puuid = summoner.get("puuid").and_then(|p| p.as_str()).map(|s| s.to_string()); metadata.puuid = summoner
.get("puuid")
.and_then(|p| p.as_str())
.map(|s| s.to_string());
// Try displayName first, then name, then internalName // Try displayName first, then name, then internalName
metadata.summoner_name = summoner.get("displayName") metadata.summoner_name = summoner
.get("displayName")
.or_else(|| summoner.get("name")) .or_else(|| summoner.get("name"))
.or_else(|| summoner.get("internalName")) .or_else(|| summoner.get("internalName"))
.and_then(|n| n.as_str()) .and_then(|n| n.as_str())
@@ -1141,8 +1148,14 @@ impl LqpClient {
// Get rune page // Get rune page
if let Ok(rune_page) = self.get_rune_page().await { if let Ok(rune_page) = self.get_rune_page().await {
let primary_style_id = rune_page.get("primaryStyleId").and_then(|id| id.as_u64()).unwrap_or(0) as u32; let primary_style_id = rune_page
let secondary_style_id = rune_page.get("subStyleId").and_then(|id| id.as_u64()).unwrap_or(0) as u32; .get("primaryStyleId")
.and_then(|id| id.as_u64())
.unwrap_or(0) as u32;
let secondary_style_id = rune_page
.get("subStyleId")
.and_then(|id| id.as_u64())
.unwrap_or(0) as u32;
let selected_perks = rune_page let selected_perks = rune_page
.get("selectedPerkIds") .get("selectedPerkIds")
.and_then(|ids| ids.as_array()) .and_then(|ids| ids.as_array())
@@ -1159,36 +1172,56 @@ impl LqpClient {
secondary_style_id, secondary_style_id,
selected_perks, selected_perks,
stat_modifiers: Vec::new(), // Stat modifiers are part of selectedPerkIds stat_modifiers: Vec::new(), // Stat modifiers are part of selectedPerkIds
name: rune_page.get("name").and_then(|n| n.as_str()).map(|s| s.to_string()), name: rune_page
current: rune_page.get("current").and_then(|c| c.as_bool()).unwrap_or(true), .get("name")
.and_then(|n| n.as_str())
.map(|s| s.to_string()),
current: rune_page
.get("current")
.and_then(|c| c.as_bool())
.unwrap_or(true),
}); });
} }
} }
// Try to get summoner spells from live client data (available during game) // Try to get summoner spells from live client data (available during game)
if let Ok(active_player) = self.get_live_client_active_player().await { if let Ok(active_player) = self.get_live_client_active_player().await {
debug!("[METADATA] Live client active player data: {:?}", active_player); debug!(
"[METADATA] Live client active player data: {:?}",
active_player
);
// Get summoner spells from active player - try multiple field structures // Get summoner spells from active player - try multiple field structures
if let Some(summoner_spells) = active_player.get("summonerSpells") { if let Some(summoner_spells) = active_player.get("summonerSpells") {
// Try nested structure first: summonerSpells.summonerSpellOne.spellId // Try nested structure first: summonerSpells.summonerSpellOne.spellId
let spell1_id = summoner_spells.get("summonerSpellOne") let spell1_id = summoner_spells
.get("summonerSpellOne")
.and_then(|s| s.get("spellId")) .and_then(|s| s.get("spellId"))
.and_then(|id| id.as_u64()) .and_then(|id| id.as_u64())
.unwrap_or_else(|| { .unwrap_or_else(|| {
// Fallback: direct spell1Id field // Fallback: direct spell1Id field
summoner_spells.get("spell1Id").and_then(|id| id.as_u64()).unwrap_or(0) summoner_spells
.get("spell1Id")
.and_then(|id| id.as_u64())
.unwrap_or(0)
}) as u32; }) as u32;
let spell2_id = summoner_spells.get("summonerSpellTwo") let spell2_id = summoner_spells
.get("summonerSpellTwo")
.and_then(|s| s.get("spellId")) .and_then(|s| s.get("spellId"))
.and_then(|id| id.as_u64()) .and_then(|id| id.as_u64())
.unwrap_or_else(|| { .unwrap_or_else(|| {
// Fallback: direct spell2Id field // Fallback: direct spell2Id field
summoner_spells.get("spell2Id").and_then(|id| id.as_u64()).unwrap_or(0) summoner_spells
.get("spell2Id")
.and_then(|id| id.as_u64())
.unwrap_or(0)
}) as u32; }) as u32;
debug!("[METADATA] Summoner spells from live client: spell1={}, spell2={}", spell1_id, spell2_id); debug!(
"[METADATA] Summoner spells from live client: spell1={}, spell2={}",
spell1_id, spell2_id
);
if spell1_id > 0 || spell2_id > 0 { if spell1_id > 0 || spell2_id > 0 {
metadata.summoner_spells = Some(SummonerSpells { metadata.summoner_spells = Some(SummonerSpells {
@@ -1201,8 +1234,11 @@ impl LqpClient {
} }
// Get summoner name from active player if not already set // Get summoner name from active player if not already set
if metadata.summoner_name.is_none() || metadata.summoner_name.as_ref().map_or(true, |n| n.is_empty()) { if metadata.summoner_name.is_none()
metadata.summoner_name = active_player.get("summonerName") || metadata.summoner_name.as_ref().is_none_or(|n| n.is_empty())
{
metadata.summoner_name = active_player
.get("summonerName")
.or_else(|| active_player.get("displayName")) .or_else(|| active_player.get("displayName"))
.or_else(|| active_player.get("riotId")) .or_else(|| active_player.get("riotId"))
.and_then(|n| n.as_str()) .and_then(|n| n.as_str())
@@ -1220,9 +1256,19 @@ impl LqpClient {
for team_key in &["teamOne", "teamTwo"] { for team_key in &["teamOne", "teamTwo"] {
if let Some(team) = game_data.get(team_key).and_then(|t| t.as_array()) { if let Some(team) = game_data.get(team_key).and_then(|t| t.as_array()) {
for player in team { for player in team {
if player.get("puuid").and_then(|p| p.as_str()) == Some(local_puuid.as_str()) { if player.get("puuid").and_then(|p| p.as_str())
let spell1_id = player.get("spell1Id").and_then(|id| id.as_u64()).unwrap_or(0) as u32; == Some(local_puuid.as_str())
let spell2_id = player.get("spell2Id").and_then(|id| id.as_u64()).unwrap_or(0) as u32; {
let spell1_id = player
.get("spell1Id")
.and_then(|id| id.as_u64())
.unwrap_or(0)
as u32;
let spell2_id = player
.get("spell2Id")
.and_then(|id| id.as_u64())
.unwrap_or(0)
as u32;
if spell1_id > 0 || spell2_id > 0 { if spell1_id > 0 || spell2_id > 0 {
metadata.summoner_spells = Some(SummonerSpells { metadata.summoner_spells = Some(SummonerSpells {
@@ -1233,8 +1279,14 @@ impl LqpClient {
}); });
} }
metadata.champion_id = player.get("championId").and_then(|id| id.as_u64()).map(|v| v as u32); metadata.champion_id = player
metadata.team = player.get("teamId").and_then(|id| id.as_u64()).map(|v| v as u32); .get("championId")
.and_then(|id| id.as_u64())
.map(|v| v as u32);
metadata.team = player
.get("teamId")
.and_then(|id| id.as_u64())
.map(|v| v as u32);
break; break;
} }
} }
@@ -1263,16 +1315,21 @@ impl LqpClient {
if let Some(arr) = player_list.as_array() { if let Some(arr) = player_list.as_array() {
for player in arr { for player in arr {
// Get summoner name - try multiple fields // Get summoner name - try multiple fields
let summoner_name = player.get("summonerName") let summoner_name = player
.get("summonerName")
.or_else(|| player.get("riotId")) .or_else(|| player.get("riotId"))
.and_then(|n| n.as_str()) .and_then(|n| n.as_str())
.unwrap_or(""); .unwrap_or("");
// Get team from teamId // Get team from teamId
let team = player.get("team").and_then(|t| t.as_u64()).map(|v| v as u32); let team = player
.get("team")
.and_then(|t| t.as_u64())
.map(|v| v as u32);
// Get champion name // Get champion name
let champion_name = player.get("championName") let champion_name = player
.get("championName")
.and_then(|n| n.as_str()) .and_then(|n| n.as_str())
.map(|s| s.to_string()); .map(|s| s.to_string());
@@ -1294,19 +1351,28 @@ impl LqpClient {
if let Ok(session) = self.get_session().await { if let Ok(session) = self.get_session().await {
if let Some(game_data) = session.get("gameData") { if let Some(game_data) = session.get("gameData") {
for team_key in &["teamOne", "teamTwo"] { for team_key in &["teamOne", "teamTwo"] {
let team_id = if *team_key == "teamOne" { 100u32 } else { 200u32 }; let team_id = if *team_key == "teamOne" {
100u32
} else {
200u32
};
if let Some(team) = game_data.get(team_key).and_then(|t| t.as_array()) { if let Some(team) = game_data.get(team_key).and_then(|t| t.as_array()) {
for player in team { for player in team {
if let (Some(puuid), Some(summoner_name)) = ( if let (Some(puuid), Some(summoner_name)) = (
player.get("puuid").and_then(|p| p.as_str()), player.get("puuid").and_then(|p| p.as_str()),
player.get("summonerName").and_then(|n| n.as_str()), player.get("summonerName").and_then(|n| n.as_str()),
) { ) {
let champion_id = player.get("championId").and_then(|id| id.as_u64()).map(|v| v as u32); let champion_id = player
let champion_name = champion_id.and_then(|id| champion_id_to_name(id)); .get("championId")
.and_then(|id| id.as_u64())
.map(|v| v as u32);
let champion_name = champion_id.and_then(champion_id_to_name);
players.push(super::PlayerIdentity { players.push(super::PlayerIdentity {
puuid: puuid.to_string(), puuid: puuid.to_string(),
summoner_name: summoner_name.to_string(), summoner_name: summoner_name.to_string(),
summoner_id: player.get("summonerId").and_then(|id| id.as_u64()), summoner_id: player
.get("summonerId")
.and_then(|id| id.as_u64()),
champion_name, champion_name,
team: Some(team_id), team: Some(team_id),
}); });
@@ -1328,20 +1394,31 @@ impl LqpClient {
// First try live client data (available during game) // First try live client data (available during game)
match self.get_live_client_player_list().await { match self.get_live_client_player_list().await {
Ok(player_list) => { Ok(player_list) => {
info!("[ITEMS] Live client player list response: {:?}", player_list); info!(
"[ITEMS] Live client player list response: {:?}",
player_list
);
if let Some(players) = player_list.as_array() { if let Some(players) = player_list.as_array() {
info!("[ITEMS] Found {} players in live client data", players.len()); info!(
"[ITEMS] Found {} players in live client data",
players.len()
);
// Find the local player (first player or one with matching puuid) // Find the local player (first player or one with matching puuid)
for player in players { for player in players {
// Check if this is the local player // Check if this is the local player
let is_local = player.get("isLocalPlayer").and_then(|l| l.as_bool()).unwrap_or(false); let is_local = player
.get("isLocalPlayer")
.and_then(|l| l.as_bool())
.unwrap_or(false);
if is_local { if is_local {
info!("[ITEMS] Found local player in live client data"); info!("[ITEMS] Found local player in live client data");
if let Some(items) = player.get("items").and_then(|i| i.as_array()) { if let Some(items) = player.get("items").and_then(|i| i.as_array()) {
info!("[ITEMS] Items array has {} items", items.len()); info!("[ITEMS] Items array has {} items", items.len());
let item_build = self.parse_items_from_live_client(items); let item_build = self.parse_items_from_live_client(items);
if item_build.is_some() { if item_build.is_some() {
info!("[ITEMS] Successfully parsed items from live client data"); info!(
"[ITEMS] Successfully parsed items from live client data"
);
return Ok(item_build); return Ok(item_build);
} }
} else { } else {
@@ -1380,11 +1457,19 @@ impl LqpClient {
for team in teams { for team in teams {
if let Some(players) = team.get("players").and_then(|p| p.as_array()) { if let Some(players) = team.get("players").and_then(|p| p.as_array()) {
for player in players { for player in players {
let is_local = player.get("isLocalPlayer").and_then(|l| l.as_bool()).unwrap_or(false); let is_local = player
.get("isLocalPlayer")
.and_then(|l| l.as_bool())
.unwrap_or(false);
if is_local { if is_local {
info!("[ITEMS] Found local player in teams[].players[]"); info!("[ITEMS] Found local player in teams[].players[]");
if let Some(items) = player.get("items").and_then(|i| i.as_array()) { if let Some(items) =
info!("[ITEMS] Player items array has {} items", items.len()); player.get("items").and_then(|i| i.as_array())
{
info!(
"[ITEMS] Player items array has {} items",
items.len()
);
let item_build = self.parse_items_from_game_stats(items); let item_build = self.parse_items_from_game_stats(items);
if item_build.is_some() { if item_build.is_some() {
info!("[ITEMS] Successfully parsed items from teams[].players[]"); info!("[ITEMS] Successfully parsed items from teams[].players[]");
@@ -1399,7 +1484,10 @@ impl LqpClient {
// Try old players array structure (for backwards compatibility) // Try old players array structure (for backwards compatibility)
if let Some(players) = stats.get("players").and_then(|p| p.as_array()) { if let Some(players) = stats.get("players").and_then(|p| p.as_array()) {
info!("[ITEMS] Found {} players in game stats (legacy)", players.len()); info!(
"[ITEMS] Found {} players in game stats (legacy)",
players.len()
);
if let Some(player) = players.first() { if let Some(player) = players.first() {
if let Some(items) = player.get("items").and_then(|i| i.as_array()) { if let Some(items) = player.get("items").and_then(|i| i.as_array()) {
let item_build = self.parse_items_from_game_stats(items); let item_build = self.parse_items_from_game_stats(items);
@@ -1422,7 +1510,10 @@ impl LqpClient {
} }
/// Parse items from live client data format. /// Parse items from live client data format.
fn parse_items_from_live_client(&self, items: &[serde_json::Value]) -> Option<super::ItemBuild> { fn parse_items_from_live_client(
&self,
items: &[serde_json::Value],
) -> Option<super::ItemBuild> {
let mut item_list = Vec::new(); let mut item_list = Vec::new();
let mut trinket = None; let mut trinket = None;
@@ -1431,7 +1522,10 @@ impl LqpClient {
if item_id > 0 { if item_id > 0 {
let item_info = ItemInfo { let item_info = ItemInfo {
item_id: item_id as u32, item_id: item_id as u32,
name: item.get("displayName").and_then(|n| n.as_str()).map(|s| s.to_string()), name: item
.get("displayName")
.and_then(|n| n.as_str())
.map(|s| s.to_string()),
slot: slot as u32, slot: slot as u32,
}; };

View File

@@ -8,7 +8,10 @@ mod client;
mod events; mod events;
pub use auth::{LockfileCredentials, LockfileWatcher}; pub use auth::{LockfileCredentials, LockfileWatcher};
pub use client::{champion_id_to_name, spell_id_to_name, GameEndMetadata, GameflowPhase, LqpClient, PreGameMetadata}; pub use client::{
champion_id_to_name, spell_id_to_name, GameEndMetadata, GameflowPhase, LqpClient,
PreGameMetadata,
};
pub use events::{ pub use events::{
ChampSelectStartInfo, ChampionPickInfo, DeathEvent, EventData, GameEndInfo, GameEvent, ChampSelectStartInfo, ChampionPickInfo, DeathEvent, EventData, GameEndInfo, GameEvent,
GameStartInfo, GameflowSession, InGameStats, ItemBuild, ItemInfo, KillEvent, MatchInfo, GameStartInfo, GameflowSession, InGameStats, ItemBuild, ItemInfo, KillEvent, MatchInfo,

View File

@@ -443,7 +443,8 @@ impl Daemon {
}); });
// Fetch player game metadata (runes, summoner spells, puuid) as fallback // Fetch player game metadata (runes, summoner spells, puuid) as fallback
let player_metadata = self.lqp_client.fetch_player_game_metadata().await.ok(); let player_metadata =
self.lqp_client.fetch_player_game_metadata().await.ok();
let (fetched_puuid, fetched_runes, fetched_summoner_spells) = let (fetched_puuid, fetched_runes, fetched_summoner_spells) =
player_metadata.map_or((None, None, None), |m| { player_metadata.map_or((None, None, None), |m| {
(m.puuid, m.runes, m.summoner_spells) (m.puuid, m.runes, m.summoner_spells)
@@ -452,7 +453,8 @@ impl Daemon {
// Use transition values first, then fall back to fetched values // Use transition values first, then fall back to fetched values
let final_puuid = transition_puuid.or(fetched_puuid); let final_puuid = transition_puuid.or(fetched_puuid);
let final_runes = transition_runes.or(fetched_runes); let final_runes = transition_runes.or(fetched_runes);
let final_summoner_spells = transition_summoner_spells.or(fetched_summoner_spells); let final_summoner_spells =
transition_summoner_spells.or(fetched_summoner_spells);
info!( info!(
"[EVENT_HANDLER] Final values: puuid={:?}, runes={:?}, summoner_spells={:?}", "[EVENT_HANDLER] Final values: puuid={:?}, runes={:?}, summoner_spells={:?}",
@@ -460,16 +462,19 @@ impl Daemon {
); );
// Fetch all players identities for puuid mapping // Fetch all players identities for puuid mapping
let all_players_identities = self.lqp_client.fetch_all_players_identities().await.ok(); let all_players_identities =
self.lqp_client.fetch_all_players_identities().await.ok();
let all_players_info: Vec<record_daemon::timeline::PlayerIdentityInfo> = let all_players_info: Vec<record_daemon::timeline::PlayerIdentityInfo> =
all_players_identities.unwrap_or_default().into_iter().map(|p| { all_players_identities
record_daemon::timeline::PlayerIdentityInfo { .unwrap_or_default()
.into_iter()
.map(|p| record_daemon::timeline::PlayerIdentityInfo {
puuid: p.puuid, puuid: p.puuid,
summoner_name: p.summoner_name, summoner_name: p.summoner_name,
champion_name: p.champion_name, champion_name: p.champion_name,
team: p.team, team: p.team,
} })
}).collect(); .collect();
// Build game metadata for timeline // Build game metadata for timeline
let metadata_update = record_daemon::timeline::MetadataUpdate { let metadata_update = record_daemon::timeline::MetadataUpdate {
@@ -501,14 +506,18 @@ impl Daemon {
info!("[EVENT_HANDLER] start_recording completed successfully"); info!("[EVENT_HANDLER] start_recording completed successfully");
} }
} }
StateTransition::GameEnded { game_end_info, final_items: _ } => { StateTransition::GameEnded {
game_end_info,
final_items: _,
} => {
info!( info!(
"[EVENT_HANDLER] GameEnded transition with info: {:?}", "[EVENT_HANDLER] GameEnded transition with info: {:?}",
game_end_info game_end_info
); );
// Fetch final items before stopping // Fetch final items before stopping
let fetched_final_items = self.lqp_client.fetch_final_items().await.ok().flatten(); let fetched_final_items =
self.lqp_client.fetch_final_items().await.ok().flatten();
// Convert GameEndInfo to GameEndMetadata if available // Convert GameEndInfo to GameEndMetadata if available
let game_end_metadata = let game_end_metadata =
@@ -551,7 +560,10 @@ impl Daemon {
game_end_metadata game_end_metadata
); );
if let Err(e) = self.stop_recording_with_metadata(game_end_metadata, fetched_final_items).await { if let Err(e) = self
.stop_recording_with_metadata(game_end_metadata, fetched_final_items)
.await
{
error!("[EVENT_HANDLER] Failed to stop recording: {}", e); error!("[EVENT_HANDLER] Failed to stop recording: {}", e);
// Don't propagate error - keep daemon running // Don't propagate error - keep daemon running

View File

@@ -298,7 +298,8 @@ pub fn detect_hardware_encoders() -> Vec<EncoderCapability> {
info!("[ENCODER_DETECT] Starting hardware encoder detection..."); info!("[ENCODER_DETECT] Starting hardware encoder detection...");
let mut capabilities = Vec::new(); // Software encoding is always available=
let capabilities = vec![EncoderCapability::Software];
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
@@ -326,9 +327,6 @@ pub fn detect_hardware_encoders() -> Vec<EncoderCapability> {
} }
} }
// Software encoding is always available
capabilities.push(EncoderCapability::Software);
info!("[ENCODER_DETECT] Detected encoders: {:?}", capabilities); info!("[ENCODER_DETECT] Detected encoders: {:?}", capabilities);
capabilities capabilities

View File

@@ -235,19 +235,23 @@ impl DaemonStateMachine {
GameEvent::GameStart(info) => { GameEvent::GameStart(info) => {
// Extract summoner spells from session data if available // Extract summoner spells from session data if available
let summoner_spells = info.session.as_ref().and_then(|session| { let summoner_spells = info.session.as_ref().and_then(|session| {
session.player_champion_selections.first().map(|selection| { session
SummonerSpells { .player_champion_selections
.first()
.map(|selection| SummonerSpells {
spell1_id: selection.spell1_id, spell1_id: selection.spell1_id,
spell2_id: selection.spell2_id, spell2_id: selection.spell2_id,
spell1_name: crate::lqp::spell_id_to_name(selection.spell1_id), spell1_name: crate::lqp::spell_id_to_name(selection.spell1_id),
spell2_name: crate::lqp::spell_id_to_name(selection.spell2_id), spell2_name: crate::lqp::spell_id_to_name(selection.spell2_id),
}
}) })
}); });
// Extract puuid from session data if available // Extract puuid from session data if available
let puuid = info.session.as_ref().and_then(|session| { let puuid = info.session.as_ref().and_then(|session| {
session.player_champion_selections.first().map(|selection| selection.puuid.clone()) session
.player_champion_selections
.first()
.map(|selection| selection.puuid.clone())
}); });
Some(StateTransition::GameStarted { Some(StateTransition::GameStarted {