Skip to main content

nil_client/client/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4#![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}