record-daemon: refactor to record raw league data
This commit is contained in:
@@ -17,7 +17,6 @@ use super::api_types::{
|
||||
use super::auth::LockfileCredentials;
|
||||
use super::endpoints;
|
||||
use super::events::GameEvent;
|
||||
use super::mappings::{champion_id_to_name, spell_id_to_name};
|
||||
use super::state::{ClientState, GameflowPhase};
|
||||
use super::tls::create_insecure_tls_config;
|
||||
use super::websocket::parse_websocket_message;
|
||||
@@ -469,6 +468,36 @@ impl LqpClient {
|
||||
// Metadata Fetching Methods
|
||||
// =========================================================================
|
||||
|
||||
/// Fetch raw session data as JSON.
|
||||
pub async fn fetch_raw_session(&self) -> Result<serde_json::Value> {
|
||||
self.request("GET", endpoints::SESSION).await
|
||||
}
|
||||
|
||||
/// Fetch raw summoner data as JSON.
|
||||
pub async fn fetch_raw_summoner(&self) -> Result<serde_json::Value> {
|
||||
self.request("GET", endpoints::SUMMONER).await
|
||||
}
|
||||
|
||||
/// Fetch raw champion select data as JSON.
|
||||
pub async fn fetch_raw_champion_select(&self) -> Result<serde_json::Value> {
|
||||
self.request("GET", endpoints::CHAMPION_SELECT).await
|
||||
}
|
||||
|
||||
/// Fetch raw rune page data as JSON.
|
||||
pub async fn fetch_raw_rune_page(&self) -> Result<serde_json::Value> {
|
||||
self.request("GET", endpoints::RUNE_PAGES).await
|
||||
}
|
||||
|
||||
/// Fetch raw live client data as JSON.
|
||||
pub async fn fetch_raw_live_client_data(&self) -> Result<serde_json::Value> {
|
||||
self.request("GET", endpoints::LIVE_CLIENT_DATA).await
|
||||
}
|
||||
|
||||
/// Fetch raw end-of-game stats as JSON.
|
||||
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();
|
||||
@@ -511,219 +540,6 @@ impl LqpClient {
|
||||
pub async fn fetch_game_end_stats(&self) -> Result<EndOfGameStatsResponse> {
|
||||
self.get_game_stats_typed().await
|
||||
}
|
||||
|
||||
/// Fetch complete player game metadata including runes, summoner spells, and items.
|
||||
pub async fn fetch_player_game_metadata(&self) -> Result<super::PlayerGameMetadata> {
|
||||
use super::{RunePage, SummonerSpells};
|
||||
|
||||
let mut metadata = super::PlayerGameMetadata::default();
|
||||
|
||||
// Get summoner info (typed)
|
||||
if let Ok(summoner) = self.get_summoner_typed().await {
|
||||
metadata.puuid = summoner.puuid;
|
||||
metadata.summoner_name = summoner
|
||||
.display_name
|
||||
.or(summoner.name)
|
||||
.or(summoner.internal_name);
|
||||
}
|
||||
|
||||
// Get rune page (typed)
|
||||
if let Ok(rune_page) = self.get_rune_page_typed().await {
|
||||
let primary_style_id = rune_page.primary_style_id.unwrap_or(0) as u32;
|
||||
let secondary_style_id = rune_page.sub_style_id.unwrap_or(0) as u32;
|
||||
let selected_perks = rune_page
|
||||
.selected_perk_ids
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|id| *id as u32)
|
||||
.collect();
|
||||
|
||||
if primary_style_id > 0 {
|
||||
metadata.runes = Some(RunePage {
|
||||
primary_style_id,
|
||||
secondary_style_id,
|
||||
selected_perks,
|
||||
stat_modifiers: Vec::new(),
|
||||
name: rune_page.name,
|
||||
current: rune_page.current.unwrap_or(true),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get summoner spells from live client data (typed)
|
||||
if let Ok(active_player) = self.get_live_client_active_player_typed().await {
|
||||
debug!("[METADATA] Live client active player data received");
|
||||
|
||||
if let Some(ref spells) = active_player.summoner_spells {
|
||||
let spell1_id = spells
|
||||
.summoner_spell_one
|
||||
.as_ref()
|
||||
.and_then(|s| s.spell_id)
|
||||
.or(spells.spell1_id)
|
||||
.unwrap_or(0) as u32;
|
||||
|
||||
let spell2_id = spells
|
||||
.summoner_spell_two
|
||||
.as_ref()
|
||||
.and_then(|s| s.spell_id)
|
||||
.or(spells.spell2_id)
|
||||
.unwrap_or(0) as u32;
|
||||
|
||||
if spell1_id > 0 || spell2_id > 0 {
|
||||
metadata.summoner_spells = Some(SummonerSpells {
|
||||
spell1_id,
|
||||
spell2_id,
|
||||
spell1_name: spell_id_to_name(spell1_id),
|
||||
spell2_name: spell_id_to_name(spell2_id),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if metadata.summoner_name.is_none()
|
||||
|| metadata.summoner_name.as_ref().is_none_or(|n| n.is_empty())
|
||||
{
|
||||
metadata.summoner_name = active_player
|
||||
.summoner_name
|
||||
.or(active_player.display_name)
|
||||
.or(active_player.riot_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Get summoner spells from session gameData (typed)
|
||||
if metadata.summoner_spells.is_none() {
|
||||
if let Ok(session) = self.get_session_typed().await {
|
||||
if let Some(local_puuid) = &metadata.puuid {
|
||||
if let Some(ref game_data) = session.game_data {
|
||||
// Check team one
|
||||
if let Some(ref team) = game_data.team_one {
|
||||
for player in team {
|
||||
if player.puuid.as_deref() == Some(local_puuid.as_str()) {
|
||||
let spell1_id = player.spell1_id.unwrap_or(0) as u32;
|
||||
let spell2_id = player.spell2_id.unwrap_or(0) as u32;
|
||||
|
||||
if spell1_id > 0 || spell2_id > 0 {
|
||||
metadata.summoner_spells = Some(SummonerSpells {
|
||||
spell1_id,
|
||||
spell2_id,
|
||||
spell1_name: spell_id_to_name(spell1_id),
|
||||
spell2_name: spell_id_to_name(spell2_id),
|
||||
});
|
||||
}
|
||||
|
||||
metadata.champion_id = player.champion_id.map(|id| id as u32);
|
||||
metadata.team = player.team_id.map(|id| id as u32);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check team two if not found
|
||||
if metadata.summoner_spells.is_none() {
|
||||
if let Some(ref team) = game_data.team_two {
|
||||
for player in team {
|
||||
if player.puuid.as_deref() == Some(local_puuid.as_str()) {
|
||||
let spell1_id = player.spell1_id.unwrap_or(0) as u32;
|
||||
let spell2_id = player.spell2_id.unwrap_or(0) as u32;
|
||||
|
||||
if spell1_id > 0 || spell2_id > 0 {
|
||||
metadata.summoner_spells = Some(SummonerSpells {
|
||||
spell1_id,
|
||||
spell2_id,
|
||||
spell1_name: spell_id_to_name(spell1_id),
|
||||
spell2_name: spell_id_to_name(spell2_id),
|
||||
});
|
||||
}
|
||||
|
||||
metadata.champion_id =
|
||||
player.champion_id.map(|id| id as u32);
|
||||
metadata.team = player.team_id.map(|id| id as u32);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(champ_id) = metadata.champion_id {
|
||||
metadata.champion_name = champion_id_to_name(champ_id);
|
||||
}
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// Fetch all players' puuid to summoner name mapping.
|
||||
pub async fn fetch_all_players_identities(&self) -> Result<Vec<super::PlayerIdentity>> {
|
||||
let mut players = Vec::new();
|
||||
|
||||
// Try live client data first (typed)
|
||||
if let Ok(player_list) = self.get_live_client_player_list_typed().await {
|
||||
for player in &player_list.0 {
|
||||
let summoner_name = player
|
||||
.summoner_name
|
||||
.as_deref()
|
||||
.or(player.riot_id.as_deref())
|
||||
.unwrap_or("");
|
||||
|
||||
if let Some(ref puuid) = player.puuid {
|
||||
players.push(super::PlayerIdentity {
|
||||
puuid: puuid.clone(),
|
||||
summoner_name: summoner_name.to_string(),
|
||||
summoner_id: player.summoner_id,
|
||||
champion_name: player.champion_name.clone(),
|
||||
team: player.team.map(|id| id as u32),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try from gameflow session (typed)
|
||||
if players.is_empty() {
|
||||
if let Ok(session) = self.get_session_typed().await {
|
||||
if let Some(ref game_data) = session.game_data {
|
||||
// Team one (team ID 100)
|
||||
if let Some(ref team) = game_data.team_one {
|
||||
for player in team {
|
||||
if let (Some(ref puuid), Some(ref summoner_name)) =
|
||||
(&player.puuid, &player.summoner_name)
|
||||
{
|
||||
let champion_id = player.champion_id.map(|id| id as u32);
|
||||
let champion_name = champion_id.and_then(champion_id_to_name);
|
||||
players.push(super::PlayerIdentity {
|
||||
puuid: puuid.clone(),
|
||||
summoner_name: summoner_name.clone(),
|
||||
summoner_id: player.summoner_id,
|
||||
champion_name,
|
||||
team: Some(100),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Team two (team ID 200)
|
||||
if let Some(ref team) = game_data.team_two {
|
||||
for player in team {
|
||||
if let (Some(ref puuid), Some(ref summoner_name)) =
|
||||
(&player.puuid, &player.summoner_name)
|
||||
{
|
||||
let champion_id = player.champion_id.map(|id| id as u32);
|
||||
let champion_name = champion_id.and_then(champion_id_to_name);
|
||||
players.push(super::PlayerIdentity {
|
||||
puuid: puuid.clone(),
|
||||
summoner_name: summoner_name.clone(),
|
||||
summoner_id: player.summoner_id,
|
||||
champion_name,
|
||||
team: Some(200),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(players)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LqpClient {
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
//! ID to name mappings for League of Legends data.
|
||||
//!
|
||||
//! Provides conversion functions for champion IDs, summoner spell IDs,
|
||||
//! and map IDs to their human-readable names.
|
||||
|
||||
/// Convert summoner spell ID to name.
|
||||
pub fn spell_id_to_name(id: u32) -> Option<String> {
|
||||
let name = match id {
|
||||
1 => "Cleanse",
|
||||
3 => "Exhaust",
|
||||
4 => "Flash",
|
||||
6 => "Ghost",
|
||||
7 => "Heal",
|
||||
11 => "Smite",
|
||||
12 => "Teleport",
|
||||
13 => "Clarity",
|
||||
14 => "Ignite",
|
||||
21 => "Barrier",
|
||||
32 => "Mark",
|
||||
39 => "Mark",
|
||||
54 => "Placeholder",
|
||||
55 => "Placeholder",
|
||||
_ => return None,
|
||||
};
|
||||
Some(name.to_string())
|
||||
}
|
||||
|
||||
/// Convert champion ID to champion name.
|
||||
/// This is a simplified mapping for common champions.
|
||||
pub fn champion_id_to_name(id: u32) -> Option<String> {
|
||||
let name = match id {
|
||||
1 => "Annie",
|
||||
2 => "Olaf",
|
||||
3 => "Galio",
|
||||
4 => "TwistedFate",
|
||||
5 => "XinZhao",
|
||||
6 => "Urgot",
|
||||
7 => "LeBlanc",
|
||||
8 => "Vladimir",
|
||||
9 => "Fiddlesticks",
|
||||
10 => "Kayle",
|
||||
11 => "MasterYi",
|
||||
12 => "Alistar",
|
||||
13 => "Ryze",
|
||||
14 => "Sion",
|
||||
15 => "Sivir",
|
||||
16 => "Soraka",
|
||||
17 => "Teemo",
|
||||
18 => "Tristana",
|
||||
19 => "Warwick",
|
||||
20 => "Nunu",
|
||||
21 => "MissFortune",
|
||||
22 => "Ashe",
|
||||
23 => "Tryndamere",
|
||||
24 => "Jax",
|
||||
25 => "Morgana",
|
||||
26 => "Zilean",
|
||||
27 => "Singed",
|
||||
28 => "Evelynn",
|
||||
29 => "Twitch",
|
||||
30 => "Karthus",
|
||||
31 => "Cho'Gath",
|
||||
32 => "Amumu",
|
||||
33 => "Rammus",
|
||||
34 => "Anivia",
|
||||
35 => "Shaco",
|
||||
36 => "DrMundo",
|
||||
37 => "Sona",
|
||||
38 => "Kassadin",
|
||||
39 => "Irelia",
|
||||
40 => "Janna",
|
||||
41 => "Gangplank",
|
||||
42 => "Corki",
|
||||
43 => "Karma",
|
||||
44 => "Taric",
|
||||
45 => "Veigar",
|
||||
48 => "Trundle",
|
||||
50 => "Swain",
|
||||
51 => "Caitlyn",
|
||||
52 => "Blitzcrank",
|
||||
53 => "Malphite",
|
||||
54 => "Katarina",
|
||||
55 => "Nocturne",
|
||||
56 => "Maokai",
|
||||
57 => "Renekton",
|
||||
58 => "JarvanIV",
|
||||
59 => "Elise",
|
||||
60 => "Talon",
|
||||
61 => "Orianna",
|
||||
62 => "Wukong",
|
||||
63 => "Brand",
|
||||
64 => "LeeSin",
|
||||
67 => "Vayne",
|
||||
68 => "Rumble",
|
||||
69 => "Cassiopeia",
|
||||
72 => "Skarner",
|
||||
74 => "Heimerdinger",
|
||||
75 => "Nasus",
|
||||
76 => "Nidalee",
|
||||
77 => "Udyr",
|
||||
78 => "Poppy",
|
||||
79 => "Gragas",
|
||||
80 => "Pantheon",
|
||||
81 => "Ezreal",
|
||||
82 => "Mordekaiser",
|
||||
83 => "Yorick",
|
||||
84 => "Akali",
|
||||
85 => "Kennedy",
|
||||
86 => "Garen",
|
||||
89 => "Leona",
|
||||
90 => "Malzahar",
|
||||
91 => "Talon",
|
||||
92 => "Riven",
|
||||
96 => "Kog'Maw",
|
||||
98 => "Shen",
|
||||
99 => "Lux",
|
||||
101 => "Xerath",
|
||||
102 => "Shyvana",
|
||||
103 => "Ahri",
|
||||
104 => "Graves",
|
||||
105 => "Fizz",
|
||||
106 => "Volibear",
|
||||
107 => "Rengar",
|
||||
110 => "Varus",
|
||||
111 => "Nautilus",
|
||||
112 => "Viktor",
|
||||
113 => "Sejuani",
|
||||
114 => "Fiora",
|
||||
115 => "Ziggs",
|
||||
117 => "Lulu",
|
||||
119 => "Draven",
|
||||
120 => "Hecarim",
|
||||
121 => "Kha'Zix",
|
||||
122 => "Darius",
|
||||
126 => "Jayce",
|
||||
127 => "Lissandra",
|
||||
131 => "Diana",
|
||||
133 => "Quinn",
|
||||
134 => "Syndra",
|
||||
136 => "AurelionSol",
|
||||
141 => "Kayn",
|
||||
142 => "Zoe",
|
||||
143 => "Lillia",
|
||||
145 => "Samira",
|
||||
147 => "Seraphine",
|
||||
150 => "Gnar",
|
||||
154 => "Zac",
|
||||
157 => "Yasuo",
|
||||
161 => "Vel'Koz",
|
||||
163 => "Taliyah",
|
||||
164 => "Camille",
|
||||
166 => "Akshan",
|
||||
167 => "Nilah",
|
||||
201 => "Braum",
|
||||
202 => "Jhin",
|
||||
203 => "Kindred",
|
||||
222 => "Jinx",
|
||||
223 => "TahmKench",
|
||||
236 => "Lucian",
|
||||
238 => "Zed",
|
||||
240 => "Kled",
|
||||
245 => "Ekko",
|
||||
246 => "Qiyana",
|
||||
254 => "Vi",
|
||||
255 => "Janna",
|
||||
256 => "Pyke",
|
||||
257 => "Nami",
|
||||
266 => "Aatrox",
|
||||
267 => "Nami",
|
||||
268 => "Azir",
|
||||
350 => "Yuumi",
|
||||
360 => "Samira",
|
||||
412 => "Thresh",
|
||||
420 => "Illaoi",
|
||||
421 => "Rek'Sai",
|
||||
427 => "Ivern",
|
||||
429 => "Kalista",
|
||||
432 => "Bard",
|
||||
497 => "Rakan",
|
||||
498 => "Xayah",
|
||||
516 => "Ornn",
|
||||
517 => "Sylas",
|
||||
518 => "Neeko",
|
||||
523 => "Aphelios",
|
||||
526 => "Rell",
|
||||
555 => "Pyke",
|
||||
711 => "Vex",
|
||||
777 => "Yone",
|
||||
875 => "Sett",
|
||||
876 => "Lillia",
|
||||
887 => "Gwen",
|
||||
888 => "Viego",
|
||||
895 => "KSante",
|
||||
901 => "Smolder",
|
||||
902 => "Hwei",
|
||||
950 => "Naafiri",
|
||||
951 => "Briar",
|
||||
_ => return None,
|
||||
};
|
||||
Some(name.to_string())
|
||||
}
|
||||
|
||||
/// Convert map ID to map name.
|
||||
pub fn map_id_to_name(id: u64) -> Option<String> {
|
||||
let name = match id {
|
||||
1 => "Summoner's Rift",
|
||||
2 => "Summoner's Rift",
|
||||
3 => "The Proving Grounds",
|
||||
4 => "Twisted Treeline",
|
||||
8 => "The Crystal Scar",
|
||||
10 => "Twisted Treeline",
|
||||
11 => "Summoner's Rift",
|
||||
12 => "Howling Abyss",
|
||||
14 => "Butcher's Bridge",
|
||||
16 => "Cosmic Ruins",
|
||||
18 => "Valoran City Park",
|
||||
19 => "Substructure 43",
|
||||
20 => "Crash Site",
|
||||
21 => "Nexus Blitz",
|
||||
22 => "Convergence",
|
||||
23 => "Arena",
|
||||
24 => "Arena",
|
||||
25 => "Rings of Wrath",
|
||||
30 => "Swarm",
|
||||
31 => "Swarm",
|
||||
32 => "Swarm",
|
||||
33 => "Swarm",
|
||||
34 => "Swarm",
|
||||
35 => "Swarm",
|
||||
_ => return None,
|
||||
};
|
||||
Some(name.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_spell_id_to_name() {
|
||||
assert_eq!(spell_id_to_name(4), Some("Flash".to_string()));
|
||||
assert_eq!(spell_id_to_name(7), Some("Heal".to_string()));
|
||||
assert_eq!(spell_id_to_name(11), Some("Smite".to_string()));
|
||||
assert_eq!(spell_id_to_name(999), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_champion_id_to_name() {
|
||||
assert_eq!(champion_id_to_name(1), Some("Annie".to_string()));
|
||||
assert_eq!(champion_id_to_name(22), Some("Ashe".to_string()));
|
||||
assert_eq!(champion_id_to_name(157), Some("Yasuo".to_string()));
|
||||
assert_eq!(champion_id_to_name(9999), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_id_to_name() {
|
||||
assert_eq!(map_id_to_name(11), Some("Summoner's Rift".to_string()));
|
||||
assert_eq!(map_id_to_name(12), Some("Howling Abyss".to_string()));
|
||||
assert_eq!(map_id_to_name(999), None);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ mod auth;
|
||||
mod client;
|
||||
mod endpoints;
|
||||
mod events;
|
||||
mod mappings;
|
||||
mod state;
|
||||
mod tls;
|
||||
mod websocket;
|
||||
@@ -33,6 +32,5 @@ pub use events::{
|
||||
ObjectiveEvent, ObjectiveType, PlayerChampionSelection, PlayerGameMetadata, PlayerIdentity,
|
||||
QueueInfo, RunePage, RuneSlot, SummonerSpells, TeamMember,
|
||||
};
|
||||
pub use mappings::{champion_id_to_name, map_id_to_name, spell_id_to_name};
|
||||
pub use state::{ClientState, GameflowPhase};
|
||||
pub use websocket::{parse_event_from_uri, parse_websocket_message};
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use super::events::{GameEvent, GameflowSession};
|
||||
use super::mappings::map_id_to_name;
|
||||
|
||||
/// Parse a WebSocket message into a game event.
|
||||
pub fn parse_websocket_message(text: &str) -> Option<GameEvent> {
|
||||
@@ -233,22 +232,9 @@ fn parse_game_start_event(data: &serde_json::Value) -> Option<GameEvent> {
|
||||
.map(|s| s.to_string())
|
||||
});
|
||||
|
||||
// Extract map name
|
||||
let map_name = session
|
||||
.as_ref()
|
||||
.and_then(|s| s.map_id())
|
||||
.and_then(|id| map_id_to_name(id as u64))
|
||||
.or_else(|| {
|
||||
data.get("gameData")
|
||||
.and_then(|gd| gd.get("queue"))
|
||||
.and_then(|q| q.get("mapId"))
|
||||
.and_then(|id| id.as_u64())
|
||||
.and_then(map_id_to_name)
|
||||
});
|
||||
|
||||
info!(
|
||||
"Extracted game metadata: game_id={}, queue={:?}, queue_id={:?}, mode={:?}, map={:?}",
|
||||
game_id, queue_type, queue_id, game_mode, map_name
|
||||
"Extracted game metadata: game_id={}, queue={:?}, queue_id={:?}, mode={:?}",
|
||||
game_id, queue_type, queue_id, game_mode
|
||||
);
|
||||
|
||||
// Note: Player-specific data (champion, team, summoner_name) is NOT extracted here.
|
||||
@@ -263,7 +249,6 @@ fn parse_game_start_event(data: &serde_json::Value) -> Option<GameEvent> {
|
||||
"queueType": queue_type,
|
||||
"queueId": queue_id,
|
||||
"gameMode": game_mode,
|
||||
"map": map_name,
|
||||
"session": session
|
||||
}))
|
||||
.unwrap_or(GameEvent::Unknown),
|
||||
|
||||
@@ -354,24 +354,16 @@ impl Daemon {
|
||||
// Handle recording start/stop
|
||||
match transition {
|
||||
StateTransition::GameStarted => {
|
||||
// Extract data from the GameStart event that was stored in the timeline
|
||||
let (game_id, queue_type, queue_id, game_mode, map_name, session) =
|
||||
if let GameEvent::GameStart(ref info) = event {
|
||||
(
|
||||
info.game_id,
|
||||
info.queue_type.clone(),
|
||||
info.queue_id,
|
||||
info.game_mode.clone(),
|
||||
info.map_name.clone(),
|
||||
info.session.clone(),
|
||||
)
|
||||
// Extract game_id from the GameStart event
|
||||
let game_id = if let GameEvent::GameStart(ref info) = event {
|
||||
info.game_id
|
||||
} else {
|
||||
(0, None, None, None, None, None)
|
||||
0
|
||||
};
|
||||
|
||||
info!(
|
||||
"[EVENT_HANDLER] GameStarted transition - game_id: {}, queue_type: {:?}, game_mode: {:?}",
|
||||
game_id, queue_type, game_mode
|
||||
"[EVENT_HANDLER] GameStarted transition - game_id: {}",
|
||||
game_id
|
||||
);
|
||||
|
||||
// If already recording, stop the current recording first
|
||||
@@ -387,110 +379,34 @@ impl Daemon {
|
||||
|
||||
info!("[EVENT_HANDLER] Calling start_recording...");
|
||||
|
||||
// Fetch player game metadata (puuid, runes, summoner spells)
|
||||
let player_metadata =
|
||||
self.lqp_client.fetch_player_game_metadata().await.ok();
|
||||
let (puuid, runes, summoner_spells, champion_id, team, summoner_name) =
|
||||
player_metadata.map_or((None, None, None, None, None, None), |m| {
|
||||
(
|
||||
m.puuid,
|
||||
m.runes,
|
||||
m.summoner_spells,
|
||||
m.champion_id,
|
||||
m.team,
|
||||
m.summoner_name,
|
||||
)
|
||||
});
|
||||
|
||||
// If we have puuid and session, extract player-specific data from session
|
||||
let (champion, final_team, final_summoner_name, final_summoner_spells) =
|
||||
if let Some(ref p) = puuid {
|
||||
if let Some(ref sess) = session {
|
||||
let champ_id = sess.get_champion_id(p);
|
||||
let champ_name =
|
||||
champ_id.and_then(record_daemon::lqp::champion_id_to_name);
|
||||
let team_id = sess.get_team(p);
|
||||
let summoner = sess.get_summoner_name(p).map(|s| s.to_string());
|
||||
|
||||
// Extract summoner spells from session's player_champion_selections
|
||||
let spells_from_session =
|
||||
sess.get_player_selection(p).map(|selection| {
|
||||
record_daemon::lqp::SummonerSpells {
|
||||
spell1_id: selection.spell1_id,
|
||||
spell2_id: selection.spell2_id,
|
||||
spell1_name: None,
|
||||
spell2_name: None,
|
||||
}
|
||||
});
|
||||
|
||||
(
|
||||
champ_name.or_else(|| {
|
||||
champion_id
|
||||
.and_then(record_daemon::lqp::champion_id_to_name)
|
||||
}),
|
||||
team_id.or(team),
|
||||
summoner.or(summoner_name),
|
||||
spells_from_session.or(summoner_spells),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
champion_id
|
||||
.and_then(record_daemon::lqp::champion_id_to_name),
|
||||
team,
|
||||
summoner_name,
|
||||
summoner_spells,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(
|
||||
champion_id.and_then(record_daemon::lqp::champion_id_to_name),
|
||||
team,
|
||||
summoner_name,
|
||||
summoner_spells,
|
||||
)
|
||||
};
|
||||
|
||||
info!(
|
||||
"[EVENT_HANDLER] Final values: puuid={:?}, runes={:?}, summoner_spells={:?}",
|
||||
puuid, runes, final_summoner_spells
|
||||
// Fetch raw API data in parallel
|
||||
let (
|
||||
raw_session,
|
||||
raw_summoner,
|
||||
raw_champion_select,
|
||||
raw_rune_page,
|
||||
raw_live_client_data,
|
||||
) = tokio::join!(
|
||||
self.lqp_client.fetch_raw_session(),
|
||||
self.lqp_client.fetch_raw_summoner(),
|
||||
self.lqp_client.fetch_raw_champion_select(),
|
||||
self.lqp_client.fetch_raw_rune_page(),
|
||||
self.lqp_client.fetch_raw_live_client_data()
|
||||
);
|
||||
|
||||
// Fetch all players identities for puuid mapping
|
||||
let all_players_identities =
|
||||
self.lqp_client.fetch_all_players_identities().await.ok();
|
||||
let all_players_info: Vec<record_daemon::timeline::PlayerIdentityInfo> =
|
||||
all_players_identities
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|p| record_daemon::timeline::PlayerIdentityInfo {
|
||||
puuid: p.puuid,
|
||||
summoner_name: p.summoner_name,
|
||||
champion_name: p.champion_name,
|
||||
team: p.team,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Build game metadata for timeline
|
||||
// Build game metadata for timeline with raw JSON
|
||||
let metadata_update = record_daemon::timeline::MetadataUpdate {
|
||||
queue_type,
|
||||
queue_id,
|
||||
game_mode,
|
||||
map_name,
|
||||
team: final_team,
|
||||
summoner_name: final_summoner_name,
|
||||
puuid,
|
||||
runes,
|
||||
summoner_spells: final_summoner_spells,
|
||||
all_players: all_players_info,
|
||||
..Default::default()
|
||||
game_id: Some(game_id),
|
||||
raw_session: raw_session.ok(),
|
||||
raw_summoner: raw_summoner.ok(),
|
||||
raw_champion_select: raw_champion_select.ok(),
|
||||
raw_rune_page: raw_rune_page.ok(),
|
||||
raw_live_client_data: raw_live_client_data.ok(),
|
||||
raw_end_game_stats: None,
|
||||
};
|
||||
|
||||
if let Err(e) = self
|
||||
.start_recording_with_metadata(
|
||||
game_id,
|
||||
champion.as_deref(),
|
||||
metadata_update,
|
||||
)
|
||||
.start_recording_with_metadata(game_id, metadata_update)
|
||||
.await
|
||||
{
|
||||
error!("[EVENT_HANDLER] Failed to start recording: {}", e);
|
||||
@@ -503,15 +419,17 @@ impl Daemon {
|
||||
StateTransition::GameEnded => {
|
||||
info!("[EVENT_HANDLER] GameEnded transition");
|
||||
|
||||
// Fetch end-of-game stats from API
|
||||
let game_end_stats = self.lqp_client.fetch_game_end_stats().await.ok();
|
||||
// Fetch raw end-of-game stats from API
|
||||
let raw_end_game_stats =
|
||||
self.lqp_client.fetch_raw_end_game_stats().await.ok();
|
||||
|
||||
info!(
|
||||
"[EVENT_HANDLER] Game end stats from API: {:?}",
|
||||
game_end_stats
|
||||
raw_end_game_stats.is_some()
|
||||
);
|
||||
|
||||
if let Err(e) = self.stop_recording_with_metadata(game_end_stats).await {
|
||||
if let Err(e) = self.stop_recording_with_metadata(raw_end_game_stats).await
|
||||
{
|
||||
error!("[EVENT_HANDLER] Failed to stop recording: {}", e);
|
||||
|
||||
// Don't propagate error - keep daemon running
|
||||
@@ -539,19 +457,18 @@ impl Daemon {
|
||||
async fn start_recording_with_metadata(
|
||||
&self,
|
||||
game_id: u64,
|
||||
champion: Option<&str>,
|
||||
metadata_update: record_daemon::timeline::MetadataUpdate,
|
||||
) -> Result<()> {
|
||||
info!(
|
||||
"Daemon::start_recording_with_metadata called - game {} ({:?})",
|
||||
game_id, champion
|
||||
"Daemon::start_recording_with_metadata called - game {}",
|
||||
game_id
|
||||
);
|
||||
|
||||
// Create a recording entry in the timeline store first
|
||||
let recording_id = self
|
||||
.timeline_store
|
||||
.write()
|
||||
.start_recording_entry(Some(game_id), champion.map(|s| s.to_string()));
|
||||
.start_recording_entry(Some(game_id), None);
|
||||
|
||||
// Update metadata immediately with game start info
|
||||
if let Err(e) = self
|
||||
@@ -569,7 +486,6 @@ impl Daemon {
|
||||
// Clone Arc references for use in spawn_blocking
|
||||
let recording_engine = self.recording_engine.clone();
|
||||
let event_mapper = self.event_mapper.clone();
|
||||
let champion_owned = champion.map(|s| s.to_string());
|
||||
|
||||
// Use spawn_blocking to avoid blocking the async runtime
|
||||
tokio::task::spawn_blocking(move || {
|
||||
@@ -579,7 +495,7 @@ impl Daemon {
|
||||
|
||||
if let Some(ref mut engine) = *engine_guard {
|
||||
info!("Calling engine.start_recording...");
|
||||
engine.start_recording(Some(game_id), champion_owned.as_deref())?;
|
||||
engine.start_recording(Some(game_id), None)?;
|
||||
info!("engine.start_recording returned successfully");
|
||||
event_mapper.write().start();
|
||||
info!("Event mapper started");
|
||||
@@ -604,10 +520,10 @@ impl Daemon {
|
||||
self.stop_recording_with_metadata(None).await
|
||||
}
|
||||
|
||||
/// Stop recording with optional game end stats.
|
||||
/// Stop recording with optional raw game end stats JSON.
|
||||
async fn stop_recording_with_metadata(
|
||||
&self,
|
||||
game_end_stats: Option<record_daemon::lqp::EndOfGameStatsResponse>,
|
||||
raw_end_game_stats: Option<serde_json::Value>,
|
||||
) -> Result<()> {
|
||||
info!("Stopping recording");
|
||||
|
||||
@@ -618,7 +534,6 @@ impl Daemon {
|
||||
let recording_engine = self.recording_engine.clone();
|
||||
let event_mapper = self.event_mapper.clone();
|
||||
let timeline_store = self.timeline_store.clone();
|
||||
let pregame_data = self.pregame_data.read().clone();
|
||||
|
||||
// Use spawn_blocking to avoid blocking the async runtime
|
||||
tokio::task::spawn_blocking(move || {
|
||||
@@ -642,50 +557,11 @@ impl Daemon {
|
||||
}
|
||||
};
|
||||
|
||||
// Update metadata if we have it
|
||||
let mut update = record_daemon::timeline::MetadataUpdate::default();
|
||||
|
||||
// Add pre-game data
|
||||
if let Some(pregame) = pregame_data {
|
||||
// Convert champion_id to name if available
|
||||
if let Some(champion_id) = pregame.champion_id() {
|
||||
update.champion =
|
||||
record_daemon::lqp::champion_id_to_name(champion_id as u32);
|
||||
}
|
||||
update.summoner_name = pregame.summoner_name().map(|s| s.to_string());
|
||||
update.queue_id = pregame.queue_id().map(|id| id as u32);
|
||||
update.game_mode = pregame.game_mode().map(|s| s.to_string());
|
||||
update.map_name = pregame.map_name().map(|s| s.to_string());
|
||||
update.team = pregame.team().map(|id| id as u32);
|
||||
}
|
||||
|
||||
// Add game end stats from API response
|
||||
if let Some(stats) = game_end_stats {
|
||||
update.victory = Some(stats.is_victory());
|
||||
update.match_id = stats.match_id.map(|id| id.to_string());
|
||||
|
||||
// Store raw end-of-game stats as JSON
|
||||
update.raw_end_game_stats = serde_json::to_value(&stats).ok();
|
||||
|
||||
// Get local player stats
|
||||
if let Some(player) = stats.get_local_player() {
|
||||
if let Some(player_stats) = &player.stats {
|
||||
update.final_stats = Some(record_daemon::timeline::GameFinalStats {
|
||||
kills: player_stats.champions_killed.unwrap_or(0) as u32,
|
||||
deaths: player_stats.num_deaths.unwrap_or(0) as u32,
|
||||
assists: player_stats.assists.unwrap_or(0) as u32,
|
||||
creep_score: player_stats.minions_killed.unwrap_or(0) as u32,
|
||||
gold_earned: player_stats.gold_earned.unwrap_or(0) as u32,
|
||||
damage_dealt: player_stats
|
||||
.total_damage_dealt_to_champions
|
||||
.unwrap_or(0),
|
||||
damage_taken: player_stats.total_damage_taken.unwrap_or(0),
|
||||
vision_score: player_stats.vision_score.unwrap_or(0.0),
|
||||
game_duration: stats.game_length.unwrap_or(0.0),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update metadata with raw end game stats
|
||||
let update = record_daemon::timeline::MetadataUpdate {
|
||||
raw_end_game_stats,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Apply the update
|
||||
if let Err(e) = timeline_store.write().update_metadata(recording_id, update) {
|
||||
|
||||
@@ -4,18 +4,16 @@ mod mapper;
|
||||
mod store;
|
||||
|
||||
pub use mapper::EventMapper;
|
||||
pub use store::{
|
||||
GameFinalStats, MetadataUpdate, PlayerIdentityInfo, RecordingMetadata, TimelineStore,
|
||||
TimestampedEvent,
|
||||
};
|
||||
pub use store::{MetadataUpdate, RecordingMetadata, TimelineStore, TimestampedEvent};
|
||||
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::lqp::{GameEvent, RunePage, SummonerSpells};
|
||||
use crate::lqp::GameEvent;
|
||||
|
||||
/// A timeline of events for a recording.
|
||||
/// Stores raw API responses for maximum flexibility and future-proofing.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Timeline {
|
||||
/// Recording ID.
|
||||
@@ -28,51 +26,27 @@ pub struct Timeline {
|
||||
pub duration_secs: i64,
|
||||
/// Events in the timeline.
|
||||
pub events: Vec<TimestampedEvent>,
|
||||
/// Champion played.
|
||||
/// Game ID if available.
|
||||
#[serde(default)]
|
||||
pub champion: Option<String>,
|
||||
/// Skin name.
|
||||
pub game_id: Option<u64>,
|
||||
/// Raw session data from `/lol-gameflow/v1/session`.
|
||||
#[serde(default)]
|
||||
pub skin_name: Option<String>,
|
||||
/// Queue type.
|
||||
pub raw_session: Option<serde_json::Value>,
|
||||
/// Raw summoner data from `/lol-summoner/v1/current-summoner`.
|
||||
#[serde(default)]
|
||||
pub queue_type: Option<String>,
|
||||
/// Queue ID.
|
||||
pub raw_summoner: Option<serde_json::Value>,
|
||||
/// Raw champion select data from `/lol-champ-select/v1/session`.
|
||||
#[serde(default)]
|
||||
pub queue_id: Option<u32>,
|
||||
/// Game mode.
|
||||
pub raw_champion_select: Option<serde_json::Value>,
|
||||
/// Raw rune page data from `/lol-perks/v1/currentpage`.
|
||||
#[serde(default)]
|
||||
pub game_mode: Option<String>,
|
||||
/// Map name.
|
||||
pub raw_rune_page: Option<serde_json::Value>,
|
||||
/// Raw live client data from `/liveclientdata/allgamedata`.
|
||||
#[serde(default)]
|
||||
pub map_name: Option<String>,
|
||||
/// Summoner name.
|
||||
#[serde(default)]
|
||||
pub summoner_name: Option<String>,
|
||||
/// Player's PUUID.
|
||||
#[serde(default)]
|
||||
pub puuid: Option<String>,
|
||||
/// Team (100 = blue, 200 = red).
|
||||
#[serde(default)]
|
||||
pub team: Option<u32>,
|
||||
/// Whether the game was won.
|
||||
#[serde(default)]
|
||||
pub victory: Option<bool>,
|
||||
/// Final player stats.
|
||||
#[serde(default)]
|
||||
pub final_stats: Option<GameFinalStats>,
|
||||
/// Rune page at game start.
|
||||
#[serde(default)]
|
||||
pub runes: Option<RunePage>,
|
||||
/// Summoner spells.
|
||||
#[serde(default)]
|
||||
pub summoner_spells: Option<SummonerSpells>,
|
||||
/// Raw end-of-game stats JSON from the API.
|
||||
pub raw_live_client_data: Option<serde_json::Value>,
|
||||
/// Raw end-of-game stats from `/lol-end-of-game/v1/eog-stats-block`.
|
||||
#[serde(default)]
|
||||
pub raw_end_game_stats: Option<serde_json::Value>,
|
||||
/// All players in the game (puuid to summoner name mapping).
|
||||
#[serde(default)]
|
||||
pub all_players: Vec<PlayerIdentityInfo>,
|
||||
}
|
||||
|
||||
impl Timeline {
|
||||
@@ -91,21 +65,13 @@ impl Timeline {
|
||||
end_time: None,
|
||||
duration_secs: 0,
|
||||
events: Vec::new(),
|
||||
champion: None,
|
||||
skin_name: None,
|
||||
queue_type: None,
|
||||
queue_id: None,
|
||||
game_mode: None,
|
||||
map_name: None,
|
||||
summoner_name: None,
|
||||
puuid: None,
|
||||
team: None,
|
||||
victory: None,
|
||||
final_stats: None,
|
||||
runes: None,
|
||||
summoner_spells: None,
|
||||
game_id: None,
|
||||
raw_session: None,
|
||||
raw_summoner: None,
|
||||
raw_champion_select: None,
|
||||
raw_rune_page: None,
|
||||
raw_live_client_data: None,
|
||||
raw_end_game_stats: None,
|
||||
all_players: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::error::{Result, TimelineError};
|
||||
use crate::lqp::{GameEvent, RunePage, SummonerSpells};
|
||||
use crate::lqp::GameEvent;
|
||||
use crate::recording::RecordingResult;
|
||||
|
||||
/// A timestamped event in the timeline.
|
||||
@@ -32,55 +32,36 @@ pub struct TimestampedEvent {
|
||||
}
|
||||
|
||||
/// Metadata for a recording.
|
||||
/// Stores raw API responses for maximum flexibility and future-proofing.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RecordingMetadata {
|
||||
/// Unique recording ID.
|
||||
pub id: Uuid,
|
||||
/// Game ID if available.
|
||||
pub game_id: Option<u64>,
|
||||
/// Match ID if available.
|
||||
pub match_id: Option<String>,
|
||||
/// Champion played.
|
||||
pub champion: Option<String>,
|
||||
/// Skin name.
|
||||
pub skin_name: Option<String>,
|
||||
/// Queue type (ranked, normal, aram, etc.).
|
||||
pub queue_type: Option<String>,
|
||||
/// Queue ID.
|
||||
pub queue_id: Option<u32>,
|
||||
/// Game mode.
|
||||
pub game_mode: Option<String>,
|
||||
/// Map name.
|
||||
pub map_name: Option<String>,
|
||||
/// Player's summoner name.
|
||||
pub summoner_name: Option<String>,
|
||||
/// Player's PUUID.
|
||||
/// Raw session data from `/lol-gameflow/v1/session`.
|
||||
#[serde(default)]
|
||||
pub puuid: Option<String>,
|
||||
/// Team (100 = blue, 200 = red).
|
||||
pub team: Option<u32>,
|
||||
/// Whether the game was won.
|
||||
pub victory: Option<bool>,
|
||||
/// Final player stats.
|
||||
pub final_stats: Option<GameFinalStats>,
|
||||
/// Player's rune page at game start.
|
||||
pub raw_session: Option<serde_json::Value>,
|
||||
/// Raw summoner data from `/lol-summoner/v1/current-summoner`.
|
||||
#[serde(default)]
|
||||
pub runes: Option<RunePage>,
|
||||
/// Player's summoner spells.
|
||||
pub raw_summoner: Option<serde_json::Value>,
|
||||
/// Raw champion select data from `/lol-champ-select/v1/session`.
|
||||
#[serde(default)]
|
||||
pub summoner_spells: Option<SummonerSpells>,
|
||||
/// Raw end-of-game stats JSON from the API.
|
||||
pub raw_champion_select: Option<serde_json::Value>,
|
||||
/// Raw rune page data from `/lol-perks/v1/currentpage`.
|
||||
#[serde(default)]
|
||||
pub raw_rune_page: Option<serde_json::Value>,
|
||||
/// Raw live client data from `/liveclientdata/allgamedata`.
|
||||
#[serde(default)]
|
||||
pub raw_live_client_data: Option<serde_json::Value>,
|
||||
/// Raw end-of-game stats from `/lol-end-of-game/v1/eog-stats-block`.
|
||||
#[serde(default)]
|
||||
pub raw_end_game_stats: Option<serde_json::Value>,
|
||||
/// All players in the game (puuid -> summoner name mapping).
|
||||
#[serde(default)]
|
||||
pub all_players: Vec<PlayerIdentityInfo>,
|
||||
/// Recording start time.
|
||||
pub start_time: DateTime<Utc>,
|
||||
/// Recording end time.
|
||||
pub end_time: Option<DateTime<Utc>>,
|
||||
/// Recording duration.
|
||||
// #[serde(with = "chrono::serde::seconds")]
|
||||
pub duration: Duration,
|
||||
/// Output file path.
|
||||
pub file_path: Option<PathBuf>,
|
||||
@@ -92,64 +73,16 @@ pub struct RecordingMetadata {
|
||||
pub finalized: bool,
|
||||
}
|
||||
|
||||
/// Player identity information for puuid to summoner name mapping.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PlayerIdentityInfo {
|
||||
/// Player's PUUID.
|
||||
pub puuid: String,
|
||||
/// Player's summoner name.
|
||||
pub summoner_name: String,
|
||||
/// Player's champion name.
|
||||
#[serde(default)]
|
||||
pub champion_name: Option<String>,
|
||||
/// Player's team (100 or 200).
|
||||
#[serde(default)]
|
||||
pub team: Option<u32>,
|
||||
}
|
||||
|
||||
/// Final game statistics for the player.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GameFinalStats {
|
||||
/// Kills.
|
||||
pub kills: u32,
|
||||
/// Deaths.
|
||||
pub deaths: u32,
|
||||
/// Assists.
|
||||
pub assists: u32,
|
||||
/// Creep score.
|
||||
pub creep_score: u32,
|
||||
/// Gold earned.
|
||||
pub gold_earned: u32,
|
||||
/// Damage dealt.
|
||||
pub damage_dealt: u64,
|
||||
/// Damage taken.
|
||||
pub damage_taken: u64,
|
||||
/// Vision score.
|
||||
pub vision_score: f64,
|
||||
/// Game duration in seconds.
|
||||
pub game_duration: f64,
|
||||
}
|
||||
|
||||
/// Update for recording metadata.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct MetadataUpdate {
|
||||
pub champion: Option<String>,
|
||||
pub match_id: Option<String>,
|
||||
pub skin_name: Option<String>,
|
||||
pub queue_type: Option<String>,
|
||||
pub queue_id: Option<u32>,
|
||||
pub game_mode: Option<String>,
|
||||
pub map_name: Option<String>,
|
||||
pub summoner_name: Option<String>,
|
||||
pub puuid: Option<String>,
|
||||
pub team: Option<u32>,
|
||||
pub victory: Option<bool>,
|
||||
pub final_stats: Option<GameFinalStats>,
|
||||
pub runes: Option<RunePage>,
|
||||
pub summoner_spells: Option<SummonerSpells>,
|
||||
pub game_id: Option<u64>,
|
||||
pub raw_session: Option<serde_json::Value>,
|
||||
pub raw_summoner: Option<serde_json::Value>,
|
||||
pub raw_champion_select: Option<serde_json::Value>,
|
||||
pub raw_rune_page: Option<serde_json::Value>,
|
||||
pub raw_live_client_data: Option<serde_json::Value>,
|
||||
pub raw_end_game_stats: Option<serde_json::Value>,
|
||||
pub all_players: Vec<PlayerIdentityInfo>,
|
||||
}
|
||||
|
||||
impl RecordingMetadata {
|
||||
@@ -158,22 +91,12 @@ impl RecordingMetadata {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
game_id: result.game_id,
|
||||
match_id: None,
|
||||
champion: result.champion.clone(),
|
||||
skin_name: None,
|
||||
queue_type: None,
|
||||
queue_id: None,
|
||||
game_mode: None,
|
||||
map_name: None,
|
||||
summoner_name: None,
|
||||
puuid: None,
|
||||
team: None,
|
||||
victory: None,
|
||||
final_stats: None,
|
||||
runes: None,
|
||||
summoner_spells: None,
|
||||
raw_session: None,
|
||||
raw_summoner: None,
|
||||
raw_champion_select: None,
|
||||
raw_rune_page: None,
|
||||
raw_live_client_data: None,
|
||||
raw_end_game_stats: None,
|
||||
all_players: Vec::new(),
|
||||
start_time: result.start_time,
|
||||
end_time: Some(result.end_time),
|
||||
duration: result.duration,
|
||||
@@ -220,27 +143,17 @@ impl TimelineStore {
|
||||
|
||||
/// Start a new recording entry (called when recording begins).
|
||||
/// Returns the recording ID for tracking events during recording.
|
||||
pub fn start_recording_entry(&self, game_id: Option<u64>, champion: Option<String>) -> Uuid {
|
||||
pub fn start_recording_entry(&self, game_id: Option<u64>, _champion: Option<String>) -> Uuid {
|
||||
let id = Uuid::new_v4();
|
||||
let metadata = RecordingMetadata {
|
||||
id,
|
||||
game_id,
|
||||
match_id: None,
|
||||
champion,
|
||||
skin_name: None,
|
||||
queue_type: None,
|
||||
queue_id: None,
|
||||
game_mode: None,
|
||||
map_name: None,
|
||||
summoner_name: None,
|
||||
puuid: None,
|
||||
team: None,
|
||||
victory: None,
|
||||
final_stats: None,
|
||||
runes: None,
|
||||
summoner_spells: None,
|
||||
raw_session: None,
|
||||
raw_summoner: None,
|
||||
raw_champion_select: None,
|
||||
raw_rune_page: None,
|
||||
raw_live_client_data: None,
|
||||
raw_end_game_stats: None,
|
||||
all_players: Vec::new(),
|
||||
start_time: Utc::now(),
|
||||
end_time: None,
|
||||
duration: Duration::zero(),
|
||||
@@ -286,22 +199,12 @@ impl TimelineStore {
|
||||
let metadata = RecordingMetadata {
|
||||
id,
|
||||
game_id: result.game_id,
|
||||
match_id: None,
|
||||
champion: result.champion.clone(),
|
||||
skin_name: None,
|
||||
queue_type: None,
|
||||
queue_id: None,
|
||||
game_mode: None,
|
||||
map_name: None,
|
||||
summoner_name: None,
|
||||
puuid: None,
|
||||
team: None,
|
||||
victory: None,
|
||||
final_stats: None,
|
||||
runes: None,
|
||||
summoner_spells: None,
|
||||
raw_session: None,
|
||||
raw_summoner: None,
|
||||
raw_champion_select: None,
|
||||
raw_rune_page: None,
|
||||
raw_live_client_data: None,
|
||||
raw_end_game_stats: None,
|
||||
all_players: Vec::new(),
|
||||
start_time: result.start_time,
|
||||
end_time: Some(result.end_time),
|
||||
duration: result.duration,
|
||||
@@ -344,54 +247,27 @@ impl TimelineStore {
|
||||
pub fn update_metadata(&self, recording_id: Uuid, update: MetadataUpdate) -> Result<()> {
|
||||
let mut recordings = self.recordings.write();
|
||||
if let Some(metadata) = recordings.get_mut(&recording_id) {
|
||||
if let Some(champion) = update.champion {
|
||||
metadata.champion = Some(champion);
|
||||
if let Some(game_id) = update.game_id {
|
||||
metadata.game_id = Some(game_id);
|
||||
}
|
||||
if let Some(match_id) = update.match_id {
|
||||
metadata.match_id = Some(match_id);
|
||||
if let Some(raw_session) = update.raw_session {
|
||||
metadata.raw_session = Some(raw_session);
|
||||
}
|
||||
if let Some(skin_name) = update.skin_name {
|
||||
metadata.skin_name = Some(skin_name);
|
||||
if let Some(raw_summoner) = update.raw_summoner {
|
||||
metadata.raw_summoner = Some(raw_summoner);
|
||||
}
|
||||
if let Some(queue_type) = update.queue_type {
|
||||
metadata.queue_type = Some(queue_type);
|
||||
if let Some(raw_champion_select) = update.raw_champion_select {
|
||||
metadata.raw_champion_select = Some(raw_champion_select);
|
||||
}
|
||||
if let Some(queue_id) = update.queue_id {
|
||||
metadata.queue_id = Some(queue_id);
|
||||
if let Some(raw_rune_page) = update.raw_rune_page {
|
||||
metadata.raw_rune_page = Some(raw_rune_page);
|
||||
}
|
||||
if let Some(game_mode) = update.game_mode {
|
||||
metadata.game_mode = Some(game_mode);
|
||||
}
|
||||
if let Some(map_name) = update.map_name {
|
||||
metadata.map_name = Some(map_name);
|
||||
}
|
||||
if let Some(summoner_name) = update.summoner_name {
|
||||
metadata.summoner_name = Some(summoner_name);
|
||||
}
|
||||
if let Some(puuid) = update.puuid {
|
||||
metadata.puuid = Some(puuid);
|
||||
}
|
||||
if let Some(team) = update.team {
|
||||
metadata.team = Some(team);
|
||||
}
|
||||
if let Some(victory) = update.victory {
|
||||
metadata.victory = Some(victory);
|
||||
}
|
||||
if let Some(final_stats) = update.final_stats {
|
||||
metadata.final_stats = Some(final_stats);
|
||||
}
|
||||
if let Some(runes) = update.runes {
|
||||
metadata.runes = Some(runes);
|
||||
}
|
||||
if let Some(summoner_spells) = update.summoner_spells {
|
||||
metadata.summoner_spells = Some(summoner_spells);
|
||||
if let Some(raw_live_client_data) = update.raw_live_client_data {
|
||||
metadata.raw_live_client_data = Some(raw_live_client_data);
|
||||
}
|
||||
if let Some(raw_end_game_stats) = update.raw_end_game_stats {
|
||||
metadata.raw_end_game_stats = Some(raw_end_game_stats);
|
||||
}
|
||||
if !update.all_players.is_empty() {
|
||||
metadata.all_players = update.all_players;
|
||||
}
|
||||
}
|
||||
drop(recordings);
|
||||
self.persist_recording(recording_id)?;
|
||||
@@ -429,21 +305,13 @@ impl TimelineStore {
|
||||
end_time: metadata.end_time,
|
||||
duration_secs: metadata.duration.num_seconds(),
|
||||
events,
|
||||
champion: metadata.champion.clone(),
|
||||
skin_name: metadata.skin_name.clone(),
|
||||
queue_type: metadata.queue_type.clone(),
|
||||
queue_id: metadata.queue_id,
|
||||
game_mode: metadata.game_mode.clone(),
|
||||
map_name: metadata.map_name.clone(),
|
||||
summoner_name: metadata.summoner_name.clone(),
|
||||
puuid: metadata.puuid.clone(),
|
||||
team: metadata.team,
|
||||
victory: metadata.victory,
|
||||
final_stats: metadata.final_stats.clone(),
|
||||
runes: metadata.runes.clone(),
|
||||
summoner_spells: metadata.summoner_spells.clone(),
|
||||
game_id: metadata.game_id,
|
||||
raw_session: metadata.raw_session.clone(),
|
||||
raw_summoner: metadata.raw_summoner.clone(),
|
||||
raw_champion_select: metadata.raw_champion_select.clone(),
|
||||
raw_rune_page: metadata.raw_rune_page.clone(),
|
||||
raw_live_client_data: metadata.raw_live_client_data.clone(),
|
||||
raw_end_game_stats: metadata.raw_end_game_stats.clone(),
|
||||
all_players: metadata.all_players.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -480,21 +348,13 @@ impl TimelineStore {
|
||||
end_time: metadata.end_time,
|
||||
duration_secs: metadata.duration.num_seconds(),
|
||||
events,
|
||||
champion: metadata.champion,
|
||||
skin_name: metadata.skin_name,
|
||||
queue_type: metadata.queue_type,
|
||||
queue_id: metadata.queue_id,
|
||||
game_mode: metadata.game_mode,
|
||||
map_name: metadata.map_name,
|
||||
summoner_name: metadata.summoner_name,
|
||||
puuid: metadata.puuid,
|
||||
team: metadata.team,
|
||||
victory: metadata.victory,
|
||||
final_stats: metadata.final_stats,
|
||||
runes: metadata.runes,
|
||||
summoner_spells: metadata.summoner_spells,
|
||||
game_id: metadata.game_id,
|
||||
raw_session: metadata.raw_session,
|
||||
raw_summoner: metadata.raw_summoner,
|
||||
raw_champion_select: metadata.raw_champion_select,
|
||||
raw_rune_page: metadata.raw_rune_page,
|
||||
raw_live_client_data: metadata.raw_live_client_data,
|
||||
raw_end_game_stats: metadata.raw_end_game_stats,
|
||||
all_players: metadata.all_players,
|
||||
};
|
||||
|
||||
let file_path = self.storage_dir.join(format!("{}.json", id));
|
||||
@@ -518,40 +378,40 @@ impl TimelineStore {
|
||||
if path.extension().map(|e| e == "json").unwrap_or(false) {
|
||||
if let Ok(contents) = std::fs::read_to_string(&path) {
|
||||
if let Ok(timeline) = serde_json::from_str::<super::Timeline>(&contents) {
|
||||
let recording_id = timeline.recording_id;
|
||||
let start_time = timeline.start_time;
|
||||
let end_time = timeline.end_time;
|
||||
let duration = timeline.duration();
|
||||
let event_count = timeline.events.len();
|
||||
let game_id = timeline.game_id;
|
||||
let raw_session = timeline.raw_session;
|
||||
let raw_summoner = timeline.raw_summoner;
|
||||
let raw_champion_select = timeline.raw_champion_select;
|
||||
let raw_rune_page = timeline.raw_rune_page;
|
||||
let raw_live_client_data = timeline.raw_live_client_data;
|
||||
let raw_end_game_stats = timeline.raw_end_game_stats;
|
||||
let events = timeline.events;
|
||||
|
||||
let metadata = RecordingMetadata {
|
||||
id: timeline.recording_id,
|
||||
game_id: None,
|
||||
match_id: None,
|
||||
champion: None,
|
||||
skin_name: None,
|
||||
queue_type: None,
|
||||
queue_id: None,
|
||||
game_mode: None,
|
||||
map_name: None,
|
||||
summoner_name: None,
|
||||
puuid: None,
|
||||
team: None,
|
||||
victory: None,
|
||||
final_stats: None,
|
||||
runes: None,
|
||||
summoner_spells: None,
|
||||
raw_end_game_stats: None,
|
||||
all_players: Vec::new(),
|
||||
start_time: timeline.start_time,
|
||||
end_time: timeline.end_time,
|
||||
duration: timeline.duration(),
|
||||
id: recording_id,
|
||||
game_id,
|
||||
raw_session,
|
||||
raw_summoner,
|
||||
raw_champion_select,
|
||||
raw_rune_page,
|
||||
raw_live_client_data,
|
||||
raw_end_game_stats,
|
||||
start_time,
|
||||
end_time,
|
||||
duration,
|
||||
file_path: None,
|
||||
file_size: None,
|
||||
event_count: timeline.events.len(),
|
||||
event_count,
|
||||
finalized: true,
|
||||
};
|
||||
|
||||
self.recordings
|
||||
.write()
|
||||
.insert(timeline.recording_id, metadata);
|
||||
self.timelines
|
||||
.write()
|
||||
.insert(timeline.recording_id, timeline.events);
|
||||
self.recordings.write().insert(recording_id, metadata);
|
||||
self.timelines.write().insert(recording_id, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -597,6 +457,6 @@ mod tests {
|
||||
assert_eq!(recordings.len(), 1);
|
||||
|
||||
let metadata = store.get_recording(id).unwrap();
|
||||
assert_eq!(metadata.champion, Some("Ahri".to_string()));
|
||||
assert_eq!(metadata.game_id, Some(12345));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user