1#![expect(clippy::wildcard_imports)]
5
6mod auth;
7mod battle;
8mod chat;
9mod cheat;
10mod city;
11mod continent;
12mod infrastructure;
13mod military;
14mod npc;
15mod player;
16mod ranking;
17mod report;
18mod round;
19mod user;
20mod world;
21
22use crate::authorization::Authorization;
23use crate::circuit_breaker::CircuitBreaker;
24use crate::error::{Error, Result};
25use crate::http::{self, USER_AGENT};
26use crate::retry::Retry;
27use crate::server::ServerAddr;
28use crate::websocket::WebSocketClient;
29use futures::future::BoxFuture;
30use local_ip_address::local_ip;
31use nil_core::event::Event;
32use nil_core::player::PlayerId;
33use nil_core::world::config::WorldId;
34use nil_crypto::password::Password;
35use nil_payload::AuthorizeRequest;
36use nil_payload::world::LeaveRequest;
37use nil_server_types::ServerKind;
38use nil_server_types::auth::Token;
39use std::borrow::Cow;
40use std::net::{IpAddr, SocketAddrV4};
41use std::sync::nonpoison::Mutex;
42use std::sync::{Arc, Weak};
43
44pub struct Client {
45 server: ServerAddr,
46 world_id: Option<WorldId>,
47 authorization: Option<Authorization>,
48 websocket: Option<WebSocketClient>,
49 circuit_breaker: Arc<Mutex<CircuitBreaker>>,
50 retry: Retry,
51 user_agent: Cow<'static, str>,
52}
53
54#[bon::bon]
55impl Client {
56 #[inline]
57 pub fn new(server: ServerAddr) -> Self {
58 Self {
59 server,
60 world_id: None,
61 authorization: None,
62 websocket: None,
63 circuit_breaker: Arc::new(Mutex::default()),
64 retry: Retry::with_attempts(2),
65 user_agent: Cow::Borrowed(USER_AGENT),
66 }
67 }
68
69 #[inline]
70 pub fn new_local(addr: SocketAddrV4) -> Self {
71 Self::new(ServerAddr::Local { addr })
72 }
73
74 #[inline]
75 pub fn new_remote() -> Self {
76 Self::new(ServerAddr::Remote)
77 }
78
79 #[builder]
80 pub async fn update<OnEvent>(
81 &mut self,
82 #[builder(start_fn)] server: ServerAddr,
83 world_id: Option<WorldId>,
84 world_password: Option<Password>,
85 player_id: Option<PlayerId>,
86 player_password: Option<Password>,
87 authorization_token: Option<Token>,
88 on_event: Option<OnEvent>,
89 ) -> Result<()>
90 where
91 OnEvent: Fn(Event) -> BoxFuture<'static, ()> + Send + Sync + 'static,
92 {
93 self.stop().await;
94 self.world_id = world_id;
95
96 if server != self.server {
97 self.server = server;
98 self
99 .circuit_breaker
100 .set(CircuitBreaker::new());
101 }
102
103 if self.server.is_remote()
104 && let Some(token) = authorization_token
105 && let Some(id) = self.validate_token(&token).await?
106 && player_id.as_ref().is_none_or(|it| it == &id)
107 && let Ok(authorization) = Authorization::new(token)
108 {
109 self.authorization = Some(authorization);
110 } else if let Some(player) = player_id {
111 let req = AuthorizeRequest { player, password: player_password };
112 self.authorization = self
113 .authorize(req)
114 .await
115 .map(|token| Some(Authorization::new(&token)))?
116 .transpose()
117 .inspect_err(|err| tracing::error!(message = %err, error = ?err))
118 .map_err(|_| Error::FailedToAuthenticate)?;
119 }
120
121 if self.world_id.is_none()
122 && self.server.is_local()
123 && let ServerKind::Local { id } = self.get_server_kind().await?
124 {
125 self.world_id = Some(id);
126 }
127
128 if let Some(world_id) = self.world_id
129 && let Some(on_event) = on_event
130 && let Some(authorization) = self.authorization.clone()
131 {
132 let websocket = WebSocketClient::connect(server)
133 .world_id(world_id)
134 .maybe_world_password(world_password)
135 .authorization(authorization)
136 .circuit_breaker(self.circuit_breaker())
137 .user_agent(&self.user_agent)
138 .on_event(on_event)
139 .call()
140 .await?;
141
142 self.websocket = Some(websocket);
143 }
144
145 Ok(())
146 }
147
148 pub async fn stop(&mut self) {
149 if let Some(world) = self.world_id
150 && self.authorization.is_some()
151 {
152 let req = LeaveRequest { world };
153 if let Err(err) = self.leave(req).await {
154 tracing::error!(message = %err, error = ?err);
155 }
156 }
157
158 self.server = ServerAddr::Remote;
159 self.world_id = None;
160 self.authorization = None;
161 self.websocket = None;
162 }
163
164 pub fn server_addr(&self) -> ServerAddr {
165 let mut addr = self.server;
166 if let ServerAddr::Local { addr } = &mut addr
167 && addr.ip().is_loopback()
168 && let Ok(ip) = local_ip()
169 && let IpAddr::V4(ip) = ip
170 {
171 addr.set_ip(ip);
172 }
173
174 addr
175 }
176
177 #[inline]
178 pub fn server(&self) -> ServerAddr {
179 self.server
180 }
181
182 #[inline]
183 pub fn world(&self) -> Option<WorldId> {
184 self.world_id
185 }
186
187 #[inline]
188 pub fn user_agent(&self) -> &str {
189 &self.user_agent
190 }
191
192 pub fn set_user_agent(&mut self, user_agent: &str) {
193 self.user_agent = Cow::Owned(user_agent.to_owned());
194 }
195
196 #[inline]
197 pub fn is_local(&self) -> bool {
198 self.server.is_local()
199 }
200
201 #[inline]
202 pub fn is_remote(&self) -> bool {
203 self.server.is_remote()
204 }
205
206 fn circuit_breaker(&self) -> Weak<Mutex<CircuitBreaker>> {
207 Arc::downgrade(&self.circuit_breaker)
208 }
209
210 pub async fn get_server_kind(&self) -> Result<ServerKind> {
211 http::json_get("get-server-kind")
212 .server(self.server)
213 .retry(&self.retry)
214 .circuit_breaker(self.circuit_breaker())
215 .user_agent(&self.user_agent)
216 .send()
217 .await
218 }
219
220 pub async fn get_server_version(&self) -> Result<String> {
221 http::get_text("version")
222 .server(self.server)
223 .retry(&self.retry)
224 .circuit_breaker(self.circuit_breaker())
225 .user_agent(&self.user_agent)
226 .send()
227 .await
228 }
229
230 pub async fn is_ready(&self) -> bool {
231 http::get("")
232 .server(self.server)
233 .retry(&self.retry)
234 .circuit_breaker(self.circuit_breaker())
235 .user_agent(&self.user_agent)
236 .send()
237 .await
238 .map(|()| true)
239 .unwrap_or(false)
240 }
241}
242
243impl Default for Client {
244 fn default() -> Self {
245 Self::new_remote()
246 }
247}