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::error::{Error, Result};
23use crate::http::authorization::Authorization;
24use crate::http::circuit_breaker::CircuitBreaker;
25use crate::http::retry::Retry;
26use crate::http::{self, USER_AGENT};
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 .user_agent(&self.user_agent)
137 .on_event(on_event)
138 .call()
139 .await?;
140
141 self.websocket = Some(websocket);
142 }
143
144 Ok(())
145 }
146
147 pub async fn stop(&mut self) {
148 if let Some(world) = self.world_id
149 && self.authorization.is_some()
150 {
151 let req = LeaveRequest { world };
152 if let Err(err) = self.leave(req).await {
153 tracing::error!(message = %err, error = ?err);
154 }
155 }
156
157 self.server = ServerAddr::Remote;
158 self.world_id = None;
159 self.authorization = None;
160 self.websocket = None;
161 }
162
163 pub fn server_addr(&self) -> ServerAddr {
164 let mut addr = self.server;
165 if let ServerAddr::Local { addr } = &mut addr
166 && addr.ip().is_loopback()
167 && let Ok(ip) = local_ip()
168 && let IpAddr::V4(ip) = ip
169 {
170 addr.set_ip(ip);
171 }
172
173 addr
174 }
175
176 #[inline]
177 pub fn server(&self) -> ServerAddr {
178 self.server
179 }
180
181 #[inline]
182 pub fn world(&self) -> Option<WorldId> {
183 self.world_id
184 }
185
186 #[inline]
187 pub fn user_agent(&self) -> &str {
188 &self.user_agent
189 }
190
191 pub fn set_user_agent(&mut self, user_agent: &str) {
192 self.user_agent = Cow::Owned(user_agent.to_owned());
193 }
194
195 #[inline]
196 pub fn is_local(&self) -> bool {
197 self.server.is_local()
198 }
199
200 #[inline]
201 pub fn is_remote(&self) -> bool {
202 self.server.is_remote()
203 }
204
205 fn circuit_breaker(&self) -> Weak<Mutex<CircuitBreaker>> {
206 Arc::downgrade(&self.circuit_breaker)
207 }
208
209 pub async fn get_server_kind(&self) -> Result<ServerKind> {
210 http::json_get("get-server-kind")
211 .server(self.server)
212 .retry(&self.retry)
213 .circuit_breaker(self.circuit_breaker())
214 .user_agent(&self.user_agent)
215 .send()
216 .await
217 }
218
219 pub async fn get_server_version(&self) -> Result<String> {
220 http::get_text("version")
221 .server(self.server)
222 .retry(&self.retry)
223 .circuit_breaker(self.circuit_breaker())
224 .user_agent(&self.user_agent)
225 .send()
226 .await
227 }
228
229 pub async fn is_ready(&self) -> bool {
230 http::get("")
231 .server(self.server)
232 .retry(&self.retry)
233 .circuit_breaker(self.circuit_breaker())
234 .user_agent(&self.user_agent)
235 .send()
236 .await
237 .map(|()| true)
238 .unwrap_or(false)
239 }
240}
241
242impl Default for Client {
243 fn default() -> Self {
244 Self::new_remote()
245 }
246}