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::auth::LockfileCredentials;
|
||||||
use super::endpoints;
|
use super::endpoints;
|
||||||
use super::events::GameEvent;
|
use super::events::GameEvent;
|
||||||
use super::mappings::{champion_id_to_name, spell_id_to_name};
|
|
||||||
use super::state::{ClientState, GameflowPhase};
|
use super::state::{ClientState, GameflowPhase};
|
||||||
use super::tls::create_insecure_tls_config;
|
use super::tls::create_insecure_tls_config;
|
||||||
use super::websocket::parse_websocket_message;
|
use super::websocket::parse_websocket_message;
|
||||||
@@ -469,6 +468,36 @@ impl LqpClient {
|
|||||||
// Metadata Fetching Methods
|
// 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).
|
/// Fetch pre-game data (stores raw API responses directly).
|
||||||
pub async fn fetch_pregame_data(&self) -> Result<PreGameData> {
|
pub async fn fetch_pregame_data(&self) -> Result<PreGameData> {
|
||||||
let mut data = PreGameData::default();
|
let mut data = PreGameData::default();
|
||||||
@@ -511,219 +540,6 @@ impl LqpClient {
|
|||||||
pub async fn fetch_game_end_stats(&self) -> Result<EndOfGameStatsResponse> {
|
pub async fn fetch_game_end_stats(&self) -> Result<EndOfGameStatsResponse> {
|
||||||
self.get_game_stats_typed().await
|
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 {
|
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 client;
|
||||||
mod endpoints;
|
mod endpoints;
|
||||||
mod events;
|
mod events;
|
||||||
mod mappings;
|
|
||||||
mod state;
|
mod state;
|
||||||
mod tls;
|
mod tls;
|
||||||
mod websocket;
|
mod websocket;
|
||||||
@@ -33,6 +32,5 @@ pub use events::{
|
|||||||
ObjectiveEvent, ObjectiveType, PlayerChampionSelection, PlayerGameMetadata, PlayerIdentity,
|
ObjectiveEvent, ObjectiveType, PlayerChampionSelection, PlayerGameMetadata, PlayerIdentity,
|
||||||
QueueInfo, RunePage, RuneSlot, SummonerSpells, TeamMember,
|
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 state::{ClientState, GameflowPhase};
|
||||||
pub use websocket::{parse_event_from_uri, parse_websocket_message};
|
pub use websocket::{parse_event_from_uri, parse_websocket_message};
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
use super::events::{GameEvent, GameflowSession};
|
use super::events::{GameEvent, GameflowSession};
|
||||||
use super::mappings::map_id_to_name;
|
|
||||||
|
|
||||||
/// Parse a WebSocket message into a game event.
|
/// Parse a WebSocket message into a game event.
|
||||||
pub fn parse_websocket_message(text: &str) -> Option<GameEvent> {
|
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())
|
.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!(
|
info!(
|
||||||
"Extracted game metadata: game_id={}, queue={:?}, queue_id={:?}, mode={:?}, map={:?}",
|
"Extracted game metadata: game_id={}, queue={:?}, queue_id={:?}, mode={:?}",
|
||||||
game_id, queue_type, queue_id, game_mode, map_name
|
game_id, queue_type, queue_id, game_mode
|
||||||
);
|
);
|
||||||
|
|
||||||
// Note: Player-specific data (champion, team, summoner_name) is NOT extracted here.
|
// 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,
|
"queueType": queue_type,
|
||||||
"queueId": queue_id,
|
"queueId": queue_id,
|
||||||
"gameMode": game_mode,
|
"gameMode": game_mode,
|
||||||
"map": map_name,
|
|
||||||
"session": session
|
"session": session
|
||||||
}))
|
}))
|
||||||
.unwrap_or(GameEvent::Unknown),
|
.unwrap_or(GameEvent::Unknown),
|
||||||
|
|||||||
@@ -354,24 +354,16 @@ impl Daemon {
|
|||||||
// Handle recording start/stop
|
// Handle recording start/stop
|
||||||
match transition {
|
match transition {
|
||||||
StateTransition::GameStarted => {
|
StateTransition::GameStarted => {
|
||||||
// Extract data from the GameStart event that was stored in the timeline
|
// Extract game_id from the GameStart event
|
||||||
let (game_id, queue_type, queue_id, game_mode, map_name, session) =
|
let game_id = if let GameEvent::GameStart(ref info) = event {
|
||||||
if let GameEvent::GameStart(ref info) = event {
|
info.game_id
|
||||||
(
|
} else {
|
||||||
info.game_id,
|
0
|
||||||
info.queue_type.clone(),
|
};
|
||||||
info.queue_id,
|
|
||||||
info.game_mode.clone(),
|
|
||||||
info.map_name.clone(),
|
|
||||||
info.session.clone(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(0, None, None, None, None, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"[EVENT_HANDLER] GameStarted transition - game_id: {}, queue_type: {:?}, game_mode: {:?}",
|
"[EVENT_HANDLER] GameStarted transition - game_id: {}",
|
||||||
game_id, queue_type, game_mode
|
game_id
|
||||||
);
|
);
|
||||||
|
|
||||||
// If already recording, stop the current recording first
|
// If already recording, stop the current recording first
|
||||||
@@ -387,110 +379,34 @@ impl Daemon {
|
|||||||
|
|
||||||
info!("[EVENT_HANDLER] Calling start_recording...");
|
info!("[EVENT_HANDLER] Calling start_recording...");
|
||||||
|
|
||||||
// Fetch player game metadata (puuid, runes, summoner spells)
|
// Fetch raw API data in parallel
|
||||||
let player_metadata =
|
let (
|
||||||
self.lqp_client.fetch_player_game_metadata().await.ok();
|
raw_session,
|
||||||
let (puuid, runes, summoner_spells, champion_id, team, summoner_name) =
|
raw_summoner,
|
||||||
player_metadata.map_or((None, None, None, None, None, None), |m| {
|
raw_champion_select,
|
||||||
(
|
raw_rune_page,
|
||||||
m.puuid,
|
raw_live_client_data,
|
||||||
m.runes,
|
) = tokio::join!(
|
||||||
m.summoner_spells,
|
self.lqp_client.fetch_raw_session(),
|
||||||
m.champion_id,
|
self.lqp_client.fetch_raw_summoner(),
|
||||||
m.team,
|
self.lqp_client.fetch_raw_champion_select(),
|
||||||
m.summoner_name,
|
self.lqp_client.fetch_raw_rune_page(),
|
||||||
)
|
self.lqp_client.fetch_raw_live_client_data()
|
||||||
});
|
|
||||||
|
|
||||||
// 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 all players identities for puuid mapping
|
// Build game metadata for timeline with raw JSON
|
||||||
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
|
|
||||||
let metadata_update = record_daemon::timeline::MetadataUpdate {
|
let metadata_update = record_daemon::timeline::MetadataUpdate {
|
||||||
queue_type,
|
game_id: Some(game_id),
|
||||||
queue_id,
|
raw_session: raw_session.ok(),
|
||||||
game_mode,
|
raw_summoner: raw_summoner.ok(),
|
||||||
map_name,
|
raw_champion_select: raw_champion_select.ok(),
|
||||||
team: final_team,
|
raw_rune_page: raw_rune_page.ok(),
|
||||||
summoner_name: final_summoner_name,
|
raw_live_client_data: raw_live_client_data.ok(),
|
||||||
puuid,
|
raw_end_game_stats: None,
|
||||||
runes,
|
|
||||||
summoner_spells: final_summoner_spells,
|
|
||||||
all_players: all_players_info,
|
|
||||||
..Default::default()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = self
|
if let Err(e) = self
|
||||||
.start_recording_with_metadata(
|
.start_recording_with_metadata(game_id, metadata_update)
|
||||||
game_id,
|
|
||||||
champion.as_deref(),
|
|
||||||
metadata_update,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
error!("[EVENT_HANDLER] Failed to start recording: {}", e);
|
error!("[EVENT_HANDLER] Failed to start recording: {}", e);
|
||||||
@@ -503,15 +419,17 @@ impl Daemon {
|
|||||||
StateTransition::GameEnded => {
|
StateTransition::GameEnded => {
|
||||||
info!("[EVENT_HANDLER] GameEnded transition");
|
info!("[EVENT_HANDLER] GameEnded transition");
|
||||||
|
|
||||||
// Fetch end-of-game stats from API
|
// Fetch raw end-of-game stats from API
|
||||||
let game_end_stats = self.lqp_client.fetch_game_end_stats().await.ok();
|
let raw_end_game_stats =
|
||||||
|
self.lqp_client.fetch_raw_end_game_stats().await.ok();
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"[EVENT_HANDLER] Game end stats from API: {:?}",
|
"[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);
|
error!("[EVENT_HANDLER] Failed to stop recording: {}", e);
|
||||||
|
|
||||||
// Don't propagate error - keep daemon running
|
// Don't propagate error - keep daemon running
|
||||||
@@ -539,19 +457,18 @@ impl Daemon {
|
|||||||
async fn start_recording_with_metadata(
|
async fn start_recording_with_metadata(
|
||||||
&self,
|
&self,
|
||||||
game_id: u64,
|
game_id: u64,
|
||||||
champion: Option<&str>,
|
|
||||||
metadata_update: record_daemon::timeline::MetadataUpdate,
|
metadata_update: record_daemon::timeline::MetadataUpdate,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
info!(
|
info!(
|
||||||
"Daemon::start_recording_with_metadata called - game {} ({:?})",
|
"Daemon::start_recording_with_metadata called - game {}",
|
||||||
game_id, champion
|
game_id
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a recording entry in the timeline store first
|
// Create a recording entry in the timeline store first
|
||||||
let recording_id = self
|
let recording_id = self
|
||||||
.timeline_store
|
.timeline_store
|
||||||
.write()
|
.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
|
// Update metadata immediately with game start info
|
||||||
if let Err(e) = self
|
if let Err(e) = self
|
||||||
@@ -569,7 +486,6 @@ impl Daemon {
|
|||||||
// Clone Arc references for use in spawn_blocking
|
// Clone Arc references for use in spawn_blocking
|
||||||
let recording_engine = self.recording_engine.clone();
|
let recording_engine = self.recording_engine.clone();
|
||||||
let event_mapper = self.event_mapper.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
|
// Use spawn_blocking to avoid blocking the async runtime
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
@@ -579,7 +495,7 @@ impl Daemon {
|
|||||||
|
|
||||||
if let Some(ref mut engine) = *engine_guard {
|
if let Some(ref mut engine) = *engine_guard {
|
||||||
info!("Calling engine.start_recording...");
|
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");
|
info!("engine.start_recording returned successfully");
|
||||||
event_mapper.write().start();
|
event_mapper.write().start();
|
||||||
info!("Event mapper started");
|
info!("Event mapper started");
|
||||||
@@ -604,10 +520,10 @@ impl Daemon {
|
|||||||
self.stop_recording_with_metadata(None).await
|
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(
|
async fn stop_recording_with_metadata(
|
||||||
&self,
|
&self,
|
||||||
game_end_stats: Option<record_daemon::lqp::EndOfGameStatsResponse>,
|
raw_end_game_stats: Option<serde_json::Value>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
info!("Stopping recording");
|
info!("Stopping recording");
|
||||||
|
|
||||||
@@ -618,7 +534,6 @@ impl Daemon {
|
|||||||
let recording_engine = self.recording_engine.clone();
|
let recording_engine = self.recording_engine.clone();
|
||||||
let event_mapper = self.event_mapper.clone();
|
let event_mapper = self.event_mapper.clone();
|
||||||
let timeline_store = self.timeline_store.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
|
// Use spawn_blocking to avoid blocking the async runtime
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
@@ -642,50 +557,11 @@ impl Daemon {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update metadata if we have it
|
// Update metadata with raw end game stats
|
||||||
let mut update = record_daemon::timeline::MetadataUpdate::default();
|
let update = record_daemon::timeline::MetadataUpdate {
|
||||||
|
raw_end_game_stats,
|
||||||
// Add pre-game data
|
..Default::default()
|
||||||
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),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the update
|
// Apply the update
|
||||||
if let Err(e) = timeline_store.write().update_metadata(recording_id, update) {
|
if let Err(e) = timeline_store.write().update_metadata(recording_id, update) {
|
||||||
|
|||||||
@@ -4,18 +4,16 @@ mod mapper;
|
|||||||
mod store;
|
mod store;
|
||||||
|
|
||||||
pub use mapper::EventMapper;
|
pub use mapper::EventMapper;
|
||||||
pub use store::{
|
pub use store::{MetadataUpdate, RecordingMetadata, TimelineStore, TimestampedEvent};
|
||||||
GameFinalStats, MetadataUpdate, PlayerIdentityInfo, RecordingMetadata, TimelineStore,
|
|
||||||
TimestampedEvent,
|
|
||||||
};
|
|
||||||
|
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::lqp::{GameEvent, RunePage, SummonerSpells};
|
use crate::lqp::GameEvent;
|
||||||
|
|
||||||
/// A timeline of events for a recording.
|
/// A timeline of events for a recording.
|
||||||
|
/// Stores raw API responses for maximum flexibility and future-proofing.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Timeline {
|
pub struct Timeline {
|
||||||
/// Recording ID.
|
/// Recording ID.
|
||||||
@@ -28,51 +26,27 @@ pub struct Timeline {
|
|||||||
pub duration_secs: i64,
|
pub duration_secs: i64,
|
||||||
/// Events in the timeline.
|
/// Events in the timeline.
|
||||||
pub events: Vec<TimestampedEvent>,
|
pub events: Vec<TimestampedEvent>,
|
||||||
/// Champion played.
|
/// Game ID if available.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub champion: Option<String>,
|
pub game_id: Option<u64>,
|
||||||
/// Skin name.
|
/// Raw session data from `/lol-gameflow/v1/session`.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub skin_name: Option<String>,
|
pub raw_session: Option<serde_json::Value>,
|
||||||
/// Queue type.
|
/// Raw summoner data from `/lol-summoner/v1/current-summoner`.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub queue_type: Option<String>,
|
pub raw_summoner: Option<serde_json::Value>,
|
||||||
/// Queue ID.
|
/// Raw champion select data from `/lol-champ-select/v1/session`.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub queue_id: Option<u32>,
|
pub raw_champion_select: Option<serde_json::Value>,
|
||||||
/// Game mode.
|
/// Raw rune page data from `/lol-perks/v1/currentpage`.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub game_mode: Option<String>,
|
pub raw_rune_page: Option<serde_json::Value>,
|
||||||
/// Map name.
|
/// Raw live client data from `/liveclientdata/allgamedata`.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub map_name: Option<String>,
|
pub raw_live_client_data: Option<serde_json::Value>,
|
||||||
/// Summoner name.
|
/// Raw end-of-game stats from `/lol-end-of-game/v1/eog-stats-block`.
|
||||||
#[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.
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub raw_end_game_stats: Option<serde_json::Value>,
|
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 {
|
impl Timeline {
|
||||||
@@ -91,21 +65,13 @@ impl Timeline {
|
|||||||
end_time: None,
|
end_time: None,
|
||||||
duration_secs: 0,
|
duration_secs: 0,
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
champion: None,
|
game_id: None,
|
||||||
skin_name: None,
|
raw_session: None,
|
||||||
queue_type: None,
|
raw_summoner: None,
|
||||||
queue_id: None,
|
raw_champion_select: None,
|
||||||
game_mode: None,
|
raw_rune_page: None,
|
||||||
map_name: None,
|
raw_live_client_data: None,
|
||||||
summoner_name: None,
|
|
||||||
puuid: None,
|
|
||||||
team: None,
|
|
||||||
victory: None,
|
|
||||||
final_stats: None,
|
|
||||||
runes: None,
|
|
||||||
summoner_spells: None,
|
|
||||||
raw_end_game_stats: None,
|
raw_end_game_stats: None,
|
||||||
all_players: Vec::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::error::{Result, TimelineError};
|
use crate::error::{Result, TimelineError};
|
||||||
use crate::lqp::{GameEvent, RunePage, SummonerSpells};
|
use crate::lqp::GameEvent;
|
||||||
use crate::recording::RecordingResult;
|
use crate::recording::RecordingResult;
|
||||||
|
|
||||||
/// A timestamped event in the timeline.
|
/// A timestamped event in the timeline.
|
||||||
@@ -32,55 +32,36 @@ pub struct TimestampedEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Metadata for a recording.
|
/// Metadata for a recording.
|
||||||
|
/// Stores raw API responses for maximum flexibility and future-proofing.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct RecordingMetadata {
|
pub struct RecordingMetadata {
|
||||||
/// Unique recording ID.
|
/// Unique recording ID.
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
/// Game ID if available.
|
/// Game ID if available.
|
||||||
pub game_id: Option<u64>,
|
pub game_id: Option<u64>,
|
||||||
/// Match ID if available.
|
/// Raw session data from `/lol-gameflow/v1/session`.
|
||||||
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.
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub puuid: Option<String>,
|
pub raw_session: Option<serde_json::Value>,
|
||||||
/// Team (100 = blue, 200 = red).
|
/// Raw summoner data from `/lol-summoner/v1/current-summoner`.
|
||||||
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.
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub runes: Option<RunePage>,
|
pub raw_summoner: Option<serde_json::Value>,
|
||||||
/// Player's summoner spells.
|
/// Raw champion select data from `/lol-champ-select/v1/session`.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub summoner_spells: Option<SummonerSpells>,
|
pub raw_champion_select: Option<serde_json::Value>,
|
||||||
/// Raw end-of-game stats JSON from the API.
|
/// 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)]
|
#[serde(default)]
|
||||||
pub raw_end_game_stats: Option<serde_json::Value>,
|
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.
|
/// Recording start time.
|
||||||
pub start_time: DateTime<Utc>,
|
pub start_time: DateTime<Utc>,
|
||||||
/// Recording end time.
|
/// Recording end time.
|
||||||
pub end_time: Option<DateTime<Utc>>,
|
pub end_time: Option<DateTime<Utc>>,
|
||||||
/// Recording duration.
|
/// Recording duration.
|
||||||
// #[serde(with = "chrono::serde::seconds")]
|
|
||||||
pub duration: Duration,
|
pub duration: Duration,
|
||||||
/// Output file path.
|
/// Output file path.
|
||||||
pub file_path: Option<PathBuf>,
|
pub file_path: Option<PathBuf>,
|
||||||
@@ -92,64 +73,16 @@ pub struct RecordingMetadata {
|
|||||||
pub finalized: bool,
|
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.
|
/// Update for recording metadata.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct MetadataUpdate {
|
pub struct MetadataUpdate {
|
||||||
pub champion: Option<String>,
|
pub game_id: Option<u64>,
|
||||||
pub match_id: Option<String>,
|
pub raw_session: Option<serde_json::Value>,
|
||||||
pub skin_name: Option<String>,
|
pub raw_summoner: Option<serde_json::Value>,
|
||||||
pub queue_type: Option<String>,
|
pub raw_champion_select: Option<serde_json::Value>,
|
||||||
pub queue_id: Option<u32>,
|
pub raw_rune_page: Option<serde_json::Value>,
|
||||||
pub game_mode: Option<String>,
|
pub raw_live_client_data: Option<serde_json::Value>,
|
||||||
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 raw_end_game_stats: Option<serde_json::Value>,
|
pub raw_end_game_stats: Option<serde_json::Value>,
|
||||||
pub all_players: Vec<PlayerIdentityInfo>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RecordingMetadata {
|
impl RecordingMetadata {
|
||||||
@@ -158,22 +91,12 @@ impl RecordingMetadata {
|
|||||||
Self {
|
Self {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
game_id: result.game_id,
|
game_id: result.game_id,
|
||||||
match_id: None,
|
raw_session: None,
|
||||||
champion: result.champion.clone(),
|
raw_summoner: None,
|
||||||
skin_name: None,
|
raw_champion_select: None,
|
||||||
queue_type: None,
|
raw_rune_page: None,
|
||||||
queue_id: None,
|
raw_live_client_data: 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,
|
raw_end_game_stats: None,
|
||||||
all_players: Vec::new(),
|
|
||||||
start_time: result.start_time,
|
start_time: result.start_time,
|
||||||
end_time: Some(result.end_time),
|
end_time: Some(result.end_time),
|
||||||
duration: result.duration,
|
duration: result.duration,
|
||||||
@@ -220,27 +143,17 @@ impl TimelineStore {
|
|||||||
|
|
||||||
/// Start a new recording entry (called when recording begins).
|
/// Start a new recording entry (called when recording begins).
|
||||||
/// Returns the recording ID for tracking events during recording.
|
/// 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 id = Uuid::new_v4();
|
||||||
let metadata = RecordingMetadata {
|
let metadata = RecordingMetadata {
|
||||||
id,
|
id,
|
||||||
game_id,
|
game_id,
|
||||||
match_id: None,
|
raw_session: None,
|
||||||
champion,
|
raw_summoner: None,
|
||||||
skin_name: None,
|
raw_champion_select: None,
|
||||||
queue_type: None,
|
raw_rune_page: None,
|
||||||
queue_id: None,
|
raw_live_client_data: 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,
|
raw_end_game_stats: None,
|
||||||
all_players: Vec::new(),
|
|
||||||
start_time: Utc::now(),
|
start_time: Utc::now(),
|
||||||
end_time: None,
|
end_time: None,
|
||||||
duration: Duration::zero(),
|
duration: Duration::zero(),
|
||||||
@@ -286,22 +199,12 @@ impl TimelineStore {
|
|||||||
let metadata = RecordingMetadata {
|
let metadata = RecordingMetadata {
|
||||||
id,
|
id,
|
||||||
game_id: result.game_id,
|
game_id: result.game_id,
|
||||||
match_id: None,
|
raw_session: None,
|
||||||
champion: result.champion.clone(),
|
raw_summoner: None,
|
||||||
skin_name: None,
|
raw_champion_select: None,
|
||||||
queue_type: None,
|
raw_rune_page: None,
|
||||||
queue_id: None,
|
raw_live_client_data: 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,
|
raw_end_game_stats: None,
|
||||||
all_players: Vec::new(),
|
|
||||||
start_time: result.start_time,
|
start_time: result.start_time,
|
||||||
end_time: Some(result.end_time),
|
end_time: Some(result.end_time),
|
||||||
duration: result.duration,
|
duration: result.duration,
|
||||||
@@ -344,54 +247,27 @@ impl TimelineStore {
|
|||||||
pub fn update_metadata(&self, recording_id: Uuid, update: MetadataUpdate) -> Result<()> {
|
pub fn update_metadata(&self, recording_id: Uuid, update: MetadataUpdate) -> Result<()> {
|
||||||
let mut recordings = self.recordings.write();
|
let mut recordings = self.recordings.write();
|
||||||
if let Some(metadata) = recordings.get_mut(&recording_id) {
|
if let Some(metadata) = recordings.get_mut(&recording_id) {
|
||||||
if let Some(champion) = update.champion {
|
if let Some(game_id) = update.game_id {
|
||||||
metadata.champion = Some(champion);
|
metadata.game_id = Some(game_id);
|
||||||
}
|
}
|
||||||
if let Some(match_id) = update.match_id {
|
if let Some(raw_session) = update.raw_session {
|
||||||
metadata.match_id = Some(match_id);
|
metadata.raw_session = Some(raw_session);
|
||||||
}
|
}
|
||||||
if let Some(skin_name) = update.skin_name {
|
if let Some(raw_summoner) = update.raw_summoner {
|
||||||
metadata.skin_name = Some(skin_name);
|
metadata.raw_summoner = Some(raw_summoner);
|
||||||
}
|
}
|
||||||
if let Some(queue_type) = update.queue_type {
|
if let Some(raw_champion_select) = update.raw_champion_select {
|
||||||
metadata.queue_type = Some(queue_type);
|
metadata.raw_champion_select = Some(raw_champion_select);
|
||||||
}
|
}
|
||||||
if let Some(queue_id) = update.queue_id {
|
if let Some(raw_rune_page) = update.raw_rune_page {
|
||||||
metadata.queue_id = Some(queue_id);
|
metadata.raw_rune_page = Some(raw_rune_page);
|
||||||
}
|
}
|
||||||
if let Some(game_mode) = update.game_mode {
|
if let Some(raw_live_client_data) = update.raw_live_client_data {
|
||||||
metadata.game_mode = Some(game_mode);
|
metadata.raw_live_client_data = Some(raw_live_client_data);
|
||||||
}
|
|
||||||
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_end_game_stats) = update.raw_end_game_stats {
|
if let Some(raw_end_game_stats) = update.raw_end_game_stats {
|
||||||
metadata.raw_end_game_stats = Some(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);
|
drop(recordings);
|
||||||
self.persist_recording(recording_id)?;
|
self.persist_recording(recording_id)?;
|
||||||
@@ -429,21 +305,13 @@ impl TimelineStore {
|
|||||||
end_time: metadata.end_time,
|
end_time: metadata.end_time,
|
||||||
duration_secs: metadata.duration.num_seconds(),
|
duration_secs: metadata.duration.num_seconds(),
|
||||||
events,
|
events,
|
||||||
champion: metadata.champion.clone(),
|
game_id: metadata.game_id,
|
||||||
skin_name: metadata.skin_name.clone(),
|
raw_session: metadata.raw_session.clone(),
|
||||||
queue_type: metadata.queue_type.clone(),
|
raw_summoner: metadata.raw_summoner.clone(),
|
||||||
queue_id: metadata.queue_id,
|
raw_champion_select: metadata.raw_champion_select.clone(),
|
||||||
game_mode: metadata.game_mode.clone(),
|
raw_rune_page: metadata.raw_rune_page.clone(),
|
||||||
map_name: metadata.map_name.clone(),
|
raw_live_client_data: metadata.raw_live_client_data.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(),
|
|
||||||
raw_end_game_stats: metadata.raw_end_game_stats.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,
|
end_time: metadata.end_time,
|
||||||
duration_secs: metadata.duration.num_seconds(),
|
duration_secs: metadata.duration.num_seconds(),
|
||||||
events,
|
events,
|
||||||
champion: metadata.champion,
|
game_id: metadata.game_id,
|
||||||
skin_name: metadata.skin_name,
|
raw_session: metadata.raw_session,
|
||||||
queue_type: metadata.queue_type,
|
raw_summoner: metadata.raw_summoner,
|
||||||
queue_id: metadata.queue_id,
|
raw_champion_select: metadata.raw_champion_select,
|
||||||
game_mode: metadata.game_mode,
|
raw_rune_page: metadata.raw_rune_page,
|
||||||
map_name: metadata.map_name,
|
raw_live_client_data: metadata.raw_live_client_data,
|
||||||
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,
|
|
||||||
raw_end_game_stats: metadata.raw_end_game_stats,
|
raw_end_game_stats: metadata.raw_end_game_stats,
|
||||||
all_players: metadata.all_players,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let file_path = self.storage_dir.join(format!("{}.json", id));
|
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 path.extension().map(|e| e == "json").unwrap_or(false) {
|
||||||
if let Ok(contents) = std::fs::read_to_string(&path) {
|
if let Ok(contents) = std::fs::read_to_string(&path) {
|
||||||
if let Ok(timeline) = serde_json::from_str::<super::Timeline>(&contents) {
|
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 {
|
let metadata = RecordingMetadata {
|
||||||
id: timeline.recording_id,
|
id: recording_id,
|
||||||
game_id: None,
|
game_id,
|
||||||
match_id: None,
|
raw_session,
|
||||||
champion: None,
|
raw_summoner,
|
||||||
skin_name: None,
|
raw_champion_select,
|
||||||
queue_type: None,
|
raw_rune_page,
|
||||||
queue_id: None,
|
raw_live_client_data,
|
||||||
game_mode: None,
|
raw_end_game_stats,
|
||||||
map_name: None,
|
start_time,
|
||||||
summoner_name: None,
|
end_time,
|
||||||
puuid: None,
|
duration,
|
||||||
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(),
|
|
||||||
file_path: None,
|
file_path: None,
|
||||||
file_size: None,
|
file_size: None,
|
||||||
event_count: timeline.events.len(),
|
event_count,
|
||||||
finalized: true,
|
finalized: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.recordings
|
self.recordings.write().insert(recording_id, metadata);
|
||||||
.write()
|
self.timelines.write().insert(recording_id, events);
|
||||||
.insert(timeline.recording_id, metadata);
|
|
||||||
self.timelines
|
|
||||||
.write()
|
|
||||||
.insert(timeline.recording_id, timeline.events);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -597,6 +457,6 @@ mod tests {
|
|||||||
assert_eq!(recordings.len(), 1);
|
assert_eq!(recordings.len(), 1);
|
||||||
|
|
||||||
let metadata = store.get_recording(id).unwrap();
|
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