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::request::auth::AuthorizeRequest;
36use nil_payload::request::world::LeaveRequest;
37use nil_payload::response::server::*;
38use nil_server_types::ServerKind;
39use nil_server_types::auth::Token;
40use std::borrow::Cow;
41use std::net::{IpAddr, SocketAddrV4};
42use std::sync::nonpoison::Mutex;
43use std::sync::{Arc, Weak};
44
45pub struct Client {
46 server: ServerAddr,
47 world_id: Option<WorldId>,
48 authorization: Option<Authorization>,
49 websocket: Option<WebSocketClient>,
50 circuit_breaker: Arc<Mutex<CircuitBreaker>>,
51 retry: Retry,
52 user_agent: Cow<'static, str>,
53}
54
55#[bon::bon]
56impl Client {
57 #[inline]
58 pub fn new(server: ServerAddr) -> Self {
59 Self {
60 server,
61 world_id: None,
62 authorization: None,
63 websocket: None,
64 circuit_breaker: Arc::new(Mutex::default()),
65 retry: Retry::with_attempts(2),
66 user_agent: Cow::Borrowed(USER_AGENT),
67 }
68 }
69
70 #[inline]
71 pub fn new_local(addr: SocketAddrV4) -> Self {
72 Self::new(ServerAddr::Local { addr })
73 }
74
75 #[inline]
76 pub fn new_remote() -> Self {
77 Self::new(ServerAddr::Remote)
78 }
79
80 #[builder]
81 pub async fn update<OnEvent>(
82 &mut self,
83 #[builder(start_fn)] server: ServerAddr,
84 #[builder(into)] world_id: Option<WorldId>,
85 #[builder(into)] world_password: Option<Password>,
86 #[builder(into)] player_id: Option<PlayerId>,
87 #[builder(into)] player_password: Option<Password>,
88 #[builder(into)] authorization_token: Option<Token>,
89 on_event: Option<OnEvent>,
90 ) -> Result<()>
91 where
92 OnEvent: Fn(Event) -> BoxFuture<'static, ()> + Send + Sync + 'static,
93 {
94 let update = Update {
95 client: self,
96 server,
97 world_id,
98 world_password,
99 player_id,
100 player_password,
101 authorization_token,
102 on_event,
103 };
104
105 update.execute().await
106 }
107
108 pub async fn stop(&mut self) {
109 if let Some(world) = self.world_id
110 && self.authorization.is_some()
111 {
112 let req = LeaveRequest { world };
113 if let Err(err) = self.leave(req).await {
114 tracing::error!(message = %err, error = ?err);
115 }
116 }
117
118 self.world_id = None;
119 self.authorization = None;
120 self.websocket = None;
121 }
122
123 pub fn server_addr(&self) -> ServerAddr {
124 let mut addr = self.server;
125 if let ServerAddr::Local { addr } = &mut addr
126 && addr.ip().is_loopback()
127 && let Ok(ip) = local_ip()
128 && let IpAddr::V4(ip) = ip
129 {
130 addr.set_ip(ip);
131 }
132
133 addr
134 }
135
136 #[inline]
137 pub fn server(&self) -> ServerAddr {
138 self.server
139 }
140
141 #[inline]
142 pub fn world(&self) -> Option<WorldId> {
143 self.world_id
144 }
145
146 #[inline]
147 pub fn user_agent(&self) -> &str {
148 &self.user_agent
149 }
150
151 #[inline]
152 pub fn set_user_agent(&mut self, user_agent: &str) {
153 self.user_agent = Cow::Owned(user_agent.to_owned());
154 }
155
156 #[inline]
157 pub fn is_local(&self) -> bool {
158 self.server.is_local()
159 }
160
161 #[inline]
162 pub fn is_remote(&self) -> bool {
163 self.server.is_remote()
164 }
165
166 fn circuit_breaker(&self) -> Weak<Mutex<CircuitBreaker>> {
167 Arc::downgrade(&self.circuit_breaker)
168 }
169
170 pub async fn get_server_kind(&self) -> Result<GetServerKindResponse> {
171 http::json_get("get-server-kind")
172 .server(self.server)
173 .retry(&self.retry)
174 .circuit_breaker(self.circuit_breaker())
175 .user_agent(&self.user_agent)
176 .send()
177 .await
178 }
179
180 pub async fn is_ready(&self) -> bool {
181 http::get("")
182 .server(self.server)
183 .retry(&self.retry)
184 .circuit_breaker(self.circuit_breaker())
185 .user_agent(&self.user_agent)
186 .send()
187 .await
188 .map(|()| true)
189 .unwrap_or_else(|err| {
190 tracing::error!(message = %err, error = ?err);
191 false
192 })
193 }
194
195 pub async fn version(&self) -> Result<VersionResponse> {
196 http::json_get("version")
197 .server(self.server)
198 .retry(&self.retry)
199 .circuit_breaker(self.circuit_breaker())
200 .user_agent(&self.user_agent)
201 .send()
202 .await
203 }
204}
205
206impl Default for Client {
207 fn default() -> Self {
208 Self::new_remote()
209 }
210}
211
212struct Update<'a, OnEvent>
213where
214 OnEvent: Fn(Event) -> BoxFuture<'static, ()> + Send + Sync + 'static,
215{
216 client: &'a mut Client,
217 server: ServerAddr,
218 world_id: Option<WorldId>,
219 world_password: Option<Password>,
220 player_id: Option<PlayerId>,
221 player_password: Option<Password>,
222 authorization_token: Option<Token>,
223 on_event: Option<OnEvent>,
224}
225
226impl<OnEvent> Update<'_, OnEvent>
227where
228 OnEvent: Fn(Event) -> BoxFuture<'static, ()> + Send + Sync + 'static,
229{
230 async fn execute(self) -> Result<()> {
231 self.client.stop().await;
232 self.client.world_id = self.world_id;
233
234 if self.server != self.client.server {
235 self.client.server = self.server;
236 self
237 .client
238 .circuit_breaker
239 .set(CircuitBreaker::new());
240 }
241
242 if self.client.server.is_remote()
243 && let Some(token) = self.authorization_token
244 && let Some(id) = self.client.validate_token(&token).await?.0
245 && self
246 .player_id
247 .as_ref()
248 .is_none_or(|it| it == &id)
249 && let Ok(authorization) = Authorization::new(token)
250 {
251 self.client.authorization = Some(authorization);
252 } else if let Some(player) = self.player_id {
253 let req = AuthorizeRequest {
254 player,
255 password: self.player_password,
256 };
257
258 self.client.authorization = self
259 .client
260 .authorize(req)
261 .await
262 .map(|token| Some(Authorization::new(&token.0)))?
263 .transpose()
264 .inspect_err(|err| tracing::error!(message = %err, error = ?err))
265 .map_err(|_| Error::FailedToAuthenticate)?;
266 }
267
268 if self.client.world_id.is_none()
269 && self.client.server.is_local()
270 && let ServerKind::Local { id } = self.client.get_server_kind().await?.0
271 {
272 self.client.world_id = Some(id);
273 }
274
275 if let Some(world_id) = self.client.world_id
276 && let Some(on_event) = self.on_event
277 && let Some(authorization) = self.client.authorization.clone()
278 {
279 let websocket = WebSocketClient::connect(self.client.server)
280 .world_id(world_id)
281 .maybe_world_password(self.world_password)
282 .authorization(authorization)
283 .circuit_breaker(self.client.circuit_breaker())
284 .user_agent(&self.client.user_agent)
285 .on_event(on_event)
286 .call()
287 .await?;
288
289 self.client.websocket = Some(websocket);
290 }
291
292 Ok(())
293 }
294}