1use std::sync::Arc;
2
3use native_tls::TlsConnector;
4use serde::Serialize;
5use serde::de::DeserializeOwned;
6use thiserror::Error;
7use ureq::{Agent, AgentBuilder};
8
9use ugg_types::client_runepage::{NewRunePage, RunePage, RunePages};
10use ugg_types::client_summoner::ClientSummoner;
11
12mod lcc;
13use lcc::{LeagueClientConnector, RiotLockFile};
14
15#[derive(Error, Debug)]
16pub enum LOLClientError {
17 #[error("Unable to create TLS connector")]
18 TlsConnectorError(#[from] native_tls::Error),
19 #[error("Unable to read lockfile")]
20 LockfileReadError(#[from] lcc::LeagueConnectorError),
21 #[error("Linux is not supported")]
22 LinuxNotSupported,
23}
24
25pub struct LOLClientAPI {
26 agent: Agent,
27 lockfile: RiotLockFile,
28}
29
30impl LOLClientAPI {
31 pub fn new() -> Result<LOLClientAPI, LOLClientError> {
32 if cfg!(target_os = "linux") {
33 return Err(LOLClientError::LinuxNotSupported);
34 }
35 Ok(LOLClientAPI {
36 agent: AgentBuilder::new()
37 .tls_connector(Arc::new(
38 TlsConnector::builder()
39 .danger_accept_invalid_certs(true)
40 .build()?,
41 ))
42 .build(),
43 lockfile: LeagueClientConnector::parse_lockfile()?,
44 })
45 }
46
47 fn get_data<T: DeserializeOwned>(&self, url: &str) -> Option<T> {
48 match self
49 .agent
50 .get(url)
51 .set(
52 "Authorization",
53 &format!("Basic {}", self.lockfile.b64_auth),
54 )
55 .call()
56 {
57 Ok(response) => {
58 if response.status() == 200 {
59 response.into_json().ok()
60 } else {
61 None
62 }
63 }
64 Err(_) => None,
65 }
66 }
67
68 fn delete_data(&self, url: &str) {
69 match self
70 .agent
71 .delete(url)
72 .set(
73 "Authorization",
74 &format!("Basic {}", self.lockfile.b64_auth),
75 )
76 .call()
77 {
78 Err(_) | Ok(_) => (),
79 }
80 }
81
82 fn post_data<T: Serialize>(&self, url: &str, data: &T) {
83 match self
84 .agent
85 .post(url)
86 .set(
87 "Authorization",
88 &format!("Basic {}", self.lockfile.b64_auth),
89 )
90 .send_json(data)
91 {
92 Err(_) | Ok(_) => (),
93 }
94 }
95
96 #[must_use]
97 pub fn get_summoner_info(&self) -> Option<ClientSummoner> {
98 self.get_data::<ClientSummoner>(&format!(
99 "https://127.0.0.1:{}/lol-summoner/v1/current-summoner",
100 self.lockfile.port
101 ))
102 }
103
104 #[must_use]
105 pub fn get_current_rune_page(&self) -> Option<RunePage> {
106 match self.get_data::<RunePages>(&format!(
107 "https://127.0.0.1:{}/lol-perks/v1/pages",
108 self.lockfile.port
109 )) {
110 Some(data) => {
111 for page in &data {
112 if page.name.starts_with("uggo:") && page.is_deletable {
113 return Some(page.clone());
114 }
115 }
116 for page in &data {
117 if page.current && page.is_deletable {
118 return Some(page.clone());
119 }
120 }
121 None
122 }
123 None => None,
124 }
125 }
126
127 pub fn update_rune_page(&self, old_page_id: i64, rune_page: &NewRunePage) {
128 self.delete_data(&format!(
129 "https://127.0.0.1:{}/lol-perks/v1/pages/{}",
130 self.lockfile.port, old_page_id
131 ));
132 self.post_data::<NewRunePage>(
133 &format!(
134 "https://127.0.0.1:{}/lol-perks/v1/pages",
135 self.lockfile.port
136 ),
137 rune_page,
138 );
139 }
140}