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