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 pub fn set_user_agent(&mut self, user_agent: &str) {
152 self.user_agent = Cow::Owned(user_agent.to_owned());
153 }
154
155 #[inline]
156 pub fn is_local(&self) -> bool {
157 self.server.is_local()
158 }
159
160 #[inline]
161 pub fn is_remote(&self) -> bool {
162 self.server.is_remote()
163 }
164
165 fn circuit_breaker(&self) -> Weak<Mutex<CircuitBreaker>> {
166 Arc::downgrade(&self.circuit_breaker)
167 }
168
169 pub async fn get_server_kind(&self) -> Result<GetServerKindResponse> {
170 http::json_get("get-server-kind")
171 .server(self.server)
172 .retry(&self.retry)
173 .circuit_breaker(self.circuit_breaker())
174 .user_agent(&self.user_agent)
175 .send()
176 .await
177 }
178
179 pub async fn get_server_version(&self) -> Result<VersionResponse> {
180 http::json_get("version")
181 .server(self.server)
182 .retry(&self.retry)
183 .circuit_breaker(self.circuit_breaker())
184 .user_agent(&self.user_agent)
185 .send()
186 .await
187 }
188
189 pub async fn is_ready(&self) -> bool {
190 http::get("")
191 .server(self.server)
192 .retry(&self.retry)
193 .circuit_breaker(self.circuit_breaker())
194 .user_agent(&self.user_agent)
195 .send()
196 .await
197 .map(|()| true)
198 .unwrap_or_else(|err| {
199 tracing::error!(message = %err, error = ?err);
200 false
201 })
202 }
203}
204
205impl Default for Client {
206 fn default() -> Self {
207 Self::new_remote()
208 }
209}
210
211struct Update<'a, OnEvent>
212where
213 OnEvent: Fn(Event) -> BoxFuture<'static, ()> + Send + Sync + 'static,
214{
215 client: &'a mut Client,
216 server: ServerAddr,
217 world_id: Option<WorldId>,
218 world_password: Option<Password>,
219 player_id: Option<PlayerId>,
220 player_password: Option<Password>,
221 authorization_token: Option<Token>,
222 on_event: Option<OnEvent>,
223}
224
225impl<OnEvent> Update<'_, OnEvent>
226where
227 OnEvent: Fn(Event) -> BoxFuture<'static, ()> + Send + Sync + 'static,
228{
229 async fn execute(self) -> Result<()> {
230 self.client.stop().await;
231 self.client.world_id = self.world_id;
232
233 if self.server != self.client.server {
234 self.client.server = self.server;
235 self
236 .client
237 .circuit_breaker
238 .set(CircuitBreaker::new());
239 }
240
241 if self.client.server.is_remote()
242 && let Some(token) = self.authorization_token
243 && let Some(id) = self.client.validate_token(&token).await?.0
244 && self
245 .player_id
246 .as_ref()
247 .is_none_or(|it| it == &id)
248 && let Ok(authorization) = Authorization::new(token)
249 {
250 self.client.authorization = Some(authorization);
251 } else if let Some(player) = self.player_id {
252 let req = AuthorizeRequest {
253 player,
254 password: self.player_password,
255 };
256
257 self.client.authorization = self
258 .client
259 .authorize(req)
260 .await
261 .map(|token| Some(Authorization::new(&token.0)))?
262 .transpose()
263 .inspect_err(|err| tracing::error!(message = %err, error = ?err))
264 .map_err(|_| Error::FailedToAuthenticate)?;
265 }
266
267 if self.client.world_id.is_none()
268 && self.client.server.is_local()
269 && let ServerKind::Local { id } = self.client.get_server_kind().await?.0
270 {
271 self.client.world_id = Some(id);
272 }
273
274 if let Some(world_id) = self.client.world_id
275 && let Some(on_event) = self.on_event
276 && let Some(authorization) = self.client.authorization.clone()
277 {
278 let websocket = WebSocketClient::connect(self.client.server)
279 .world_id(world_id)
280 .maybe_world_password(self.world_password)
281 .authorization(authorization)
282 .circuit_breaker(self.client.circuit_breaker())
283 .user_agent(&self.client.user_agent)
284 .on_event(on_event)
285 .call()
286 .await?;
287
288 self.client.websocket = Some(websocket);
289 }
290
291 Ok(())
292 }
293}