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