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 world_id: Option<WorldId>,
85 world_password: Option<Password>,
86 player_id: Option<PlayerId>,
87 player_password: Option<Password>,
88 authorization_token: Option<Token>,
89 on_event: Option<OnEvent>,
90 ) -> Result<()>
91 where
92 OnEvent: Fn(Event) -> BoxFuture<'static, ()> + Send + Sync + 'static,
93 {
94 self.stop().await;
95 self.world_id = world_id;
96
97 if server != self.server {
98 self.server = server;
99 self
100 .circuit_breaker
101 .set(CircuitBreaker::new());
102 }
103
104 if self.server.is_remote()
105 && let Some(token) = authorization_token
106 && let Some(id) = self.validate_token(&token).await?.0
107 && player_id.as_ref().is_none_or(|it| it == &id)
108 && let Ok(authorization) = Authorization::new(token)
109 {
110 self.authorization = Some(authorization);
111 } else if let Some(player) = player_id {
112 let req = AuthorizeRequest { player, password: player_password };
113 self.authorization = self
114 .authorize(req)
115 .await
116 .map(|token| Some(Authorization::new(&token.0)))?
117 .transpose()
118 .inspect_err(|err| tracing::error!(message = %err, error = ?err))
119 .map_err(|_| Error::FailedToAuthenticate)?;
120 }
121
122 if self.world_id.is_none()
123 && self.server.is_local()
124 && let ServerKind::Local { id } = self.get_server_kind().await?.0
125 {
126 self.world_id = Some(id);
127 }
128
129 if let Some(world_id) = self.world_id
130 && let Some(on_event) = on_event
131 && let Some(authorization) = self.authorization.clone()
132 {
133 let websocket = WebSocketClient::connect(self.server)
134 .world_id(world_id)
135 .maybe_world_password(world_password)
136 .authorization(authorization)
137 .circuit_breaker(self.circuit_breaker())
138 .user_agent(&self.user_agent)
139 .on_event(on_event)
140 .call()
141 .await?;
142
143 self.websocket = Some(websocket);
144 }
145
146 Ok(())
147 }
148
149 pub async fn stop(&mut self) {
150 if let Some(world) = self.world_id
151 && self.authorization.is_some()
152 {
153 let req = LeaveRequest { world };
154 if let Err(err) = self.leave(req).await {
155 tracing::error!(message = %err, error = ?err);
156 }
157 }
158
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<GetServerKindResponse> {
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<VersionResponse> {
221 http::json_get("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_else(|err| {
240 tracing::error!(message = %err, error = ?err);
241 false
242 })
243 }
244}
245
246impl Default for Client {
247 fn default() -> Self {
248 Self::new_remote()
249 }
250}