record-daemon: refactor to record raw league data
This commit is contained in:
+30
-214
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user