1use crate::scheme::{deserialize_datetime_timestamp, serialize_datetime_timestamp};
2use crate::util::encoding::{encode, slugify2};
3use chrono::NaiveDateTime;
4use serde_derive::{Deserialize, Serialize};
5use std::collections::{HashMap, HashSet};
6
7const DEFAULT_LOCATION: &str = "unknown";
8const DEFAULT_CLAN_FILTERS: [&str; 2] = ["DD-Persian", "/vDQMHSss8W"];
9
10#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct DDSkinHD {
12 pub uhd: bool,
13}
14
15#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct DDSkin {
17 pub name: String,
18 pub r#type: String,
19 pub hd: DDSkinHD,
20 pub creator: String,
21 pub license: String,
22 pub bodypart: String,
23 pub gameversion: String,
24 pub date: String,
25 pub skinpack: String,
26 pub imgtype: String,
27}
28
29#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
30pub struct DDSkins {
31 pub skins: Vec<DDSkin>,
32 pub version: String,
33}
34
35impl DDSkins {
36 pub fn api() -> String {
37 "https://skins.ddnet.org/skin/skins.json".to_string()
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum MasterServer {
43 One = 1,
44 Two = 2,
45 Three = 3,
46 Four = 4,
47}
48
49impl MasterServer {
50 pub fn get_index(&self) -> i32 {
51 *self as i32
52 }
53
54 pub fn api(&self) -> String {
55 format!(
56 "https://master{}.ddnet.org/ddnet/15/servers.json",
57 self.get_index()
58 )
59 }
60}
61
62#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
63pub struct QueryMapper {
64 pub mapper: String,
65 pub num_maps: i64,
66}
67
68impl QueryMapper {
69 pub fn api(player: &str) -> String {
70 format!("https://ddnet.org/maps/?qmapper={}", encode(player))
71 }
72}
73
74#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct QueryMap {
76 pub name: String,
77 pub r#type: String,
78 pub mapper: String,
79}
80
81impl QueryMap {
82 pub fn api(player: &str) -> String {
83 format!("https://ddnet.org/maps/?query={}", encode(player))
84 }
85}
86
87#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct Query {
90 pub points: i64,
91 pub name: String,
92}
93
94impl Query {
95 pub fn api(player: &str) -> String {
96 format!("https://ddnet.org/players/?query={}", encode(player))
97 }
98}
99
100#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
101pub struct ReleasesMaps {
102 pub name: String,
103 pub website: String,
104 pub thumbnail: String,
105 pub web_preview: String,
106 pub r#type: String,
107 pub points: u8,
108 pub difficulty: u8,
109 pub mapper: String,
110 pub release: String,
111 pub width: Option<u64>,
112 pub height: Option<u64>,
113 #[serde(default)]
114 pub tiles: Vec<String>,
115}
116
117impl ReleasesMaps {
118 pub fn url() -> String {
119 "https://ddnet.org/releases".to_string()
120 }
121
122 pub fn api() -> String {
123 "https://ddnet.org/releases/maps.json".to_string()
124 }
125}
126
127#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
128pub struct StatusData {
129 pub name: String,
130 pub r#type: String,
131 pub host: String,
132 pub location: String,
133 pub online4: bool,
134 pub online6: bool,
135 pub uptime: String,
136 pub load: f32,
137 pub network_rx: u64,
138 pub network_tx: u64,
139 pub packets_rx: u64,
140 pub packets_tx: u64,
141 pub cpu: u32,
142 pub memory_total: u64,
143 pub memory_used: u64,
144 pub swap_total: u64,
145 pub swap_used: u64,
146 pub hdd_total: u64,
147 pub hdd_used: u64,
148}
149
150#[derive(Serialize, Deserialize, Debug)]
151pub struct Status {
152 pub servers: Vec<StatusData>,
153 pub updated: String,
154}
155
156impl Status {
157 pub fn url() -> String {
158 "https://ddnet.org/status".to_string()
159 }
160
161 pub fn api() -> String {
162 "https://ddnet.org/status/json/stats.json".to_string()
163 }
164}
165
166#[derive(Default, Debug, Clone)]
167pub struct ClanCount {
168 pub name: String,
169 pub count: usize,
170}
171
172#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
173#[serde(rename_all = "camelCase")]
174pub struct Master {
175 pub servers: Vec<Server>,
176}
177
178impl Master {
179 pub fn api(master: MasterServer) -> String {
180 master.api()
181 }
182
183 pub fn count_clients(&self) -> usize {
184 self.servers.iter().map(|s| s.info.clients.len()).sum()
185 }
186
187 pub fn get_clans(&self) -> Vec<ClanCount> {
188 self.get_filtered_clans(None)
189 }
190
191 pub fn get_filtered_clans(&self, filters: Option<Vec<&str>>) -> Vec<ClanCount> {
192 if self.servers.is_empty() {
193 return Vec::new();
194 }
195
196 let filter_set: HashSet<&str> = filters
197 .unwrap_or_else(|| DEFAULT_CLAN_FILTERS.to_vec())
198 .into_iter()
199 .collect();
200
201 let mut clan_counts = HashMap::new();
202
203 for server in &self.servers {
204 for client in &server.info.clients {
205 if !client.clan.is_empty() && !filter_set.contains(client.clan.as_str()) {
206 *clan_counts.entry(client.clan.clone()).or_insert(0) += 1;
207 }
208 }
209 }
210
211 let mut result: Vec<ClanCount> = clan_counts
212 .into_iter()
213 .map(|(name, count)| ClanCount { name, count })
214 .collect();
215
216 result.sort_by(|a, b| b.count.cmp(&a.count));
217 result
218 }
219}
220
221#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
222pub struct Server {
223 pub addresses: Vec<String>,
224 #[serde(default = "default_location")]
225 pub location: String,
226 pub info: Info,
227}
228
229impl Server {
230 #[allow(dead_code)]
231 pub fn count_client(&self) -> usize {
232 self.info.clients.len()
233 }
234}
235
236#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
237pub struct Info {
238 pub max_clients: i64,
239 pub max_players: i64,
240 #[serde(default)]
241 pub passworded: bool,
242 #[serde(rename = "game_type")]
243 pub gametype: String,
244 pub name: String,
245 pub map: IMap,
246 pub version: String,
247 #[serde(default)]
248 pub clients: Vec<Client>,
249 #[serde(default)]
250 pub requires_login: bool,
251 pub community: Option<Community>,
252}
253
254#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
255pub struct IMap {
256 pub name: String,
257 pub sha256: Option<String>,
258 pub size: Option<i64>,
259}
260
261#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
262pub struct Client {
263 pub name: String,
264 pub clan: String,
265 pub country: i32,
266 pub score: i64,
267 #[serde(default)]
268 pub is_player: bool,
269 pub skin: Option<Skin>,
270 #[serde(default)]
271 pub afk: bool,
272 #[serde(default)]
273 pub team: i64,
274}
275
276#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
277pub struct Skin {
278 pub name: Option<String>,
279 pub color_body: Option<i64>,
280 pub color_feet: Option<i64>,
281}
282
283#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
284pub struct Community {
285 pub id: String,
286 pub icon: String,
287 pub admin: Vec<String>,
288 pub public_key: Option<String>,
289 pub signature: Option<String>,
290}
291
292#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
293pub struct Player {
294 pub player: String,
295 pub points: Points,
296 pub team_rank: Option<Rank>,
297 pub rank: Option<Rank>,
298 pub points_last_year: Option<Rank>,
299 pub points_last_month: Option<Rank>,
300 pub points_last_week: Option<Rank>,
301 pub favorite_server: FavoriteServer,
302 pub first_finish: FirstFinish,
303 pub last_finishes: Vec<LastFinish>,
304 #[serde(default)]
305 pub favorite_partners: Vec<FavoritePartner>,
306 pub types: Types,
307 pub activity: Vec<Activity>,
308 pub hours_played_past_365_days: i64,
309}
310
311impl Player {
312 pub fn url(&self) -> String {
313 format!(
314 "https://ddnet.org/players/{}",
315 encode(&slugify2(&self.player))
316 )
317 }
318
319 pub fn url_with_name(player: &str) -> String {
320 format!("https://ddnet.org/players/{}", encode(&slugify2(player)))
321 }
322
323 pub fn api(player: &str) -> String {
324 format!("https://ddnet.org/players/?json2={}", encode(player))
325 }
326}
327
328#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
329pub struct Map {
330 pub name: String,
331 pub website: String,
332 pub thumbnail: String,
333 pub web_preview: String,
334 pub r#type: String,
335 pub points: i64,
336 pub difficulty: i64,
337 pub mapper: String,
338 pub release: Option<f64>,
339 pub median_time: f64,
340 pub first_finish: f64,
341 pub last_finish: f64,
342 pub finishes: i64,
343 pub finishers: i64,
344 pub biggest_team: i64,
345 pub width: i64,
346 pub height: i64,
347 pub tiles: Vec<String>,
348 pub team_ranks: Vec<DTeamRank>,
349 pub ranks: Vec<DRank>,
350 pub max_finishes: Vec<MaxFinish>,
351}
352
353impl Map {
354 pub fn url(&self) -> String {
355 format!("https://ddnet.org/maps/{}", encode(&slugify2(&self.name)))
356 }
357
358 pub fn url_with_name(map: &str) -> String {
359 format!("https://ddnet.org/maps/{}", encode(&slugify2(map)))
360 }
361
362 pub fn api(map: &str) -> String {
363 format!("https://ddnet.org/maps/?json={}", encode(map))
364 }
365}
366
367#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
368pub struct DTeamRank {
369 pub rank: i64,
370 pub players: Vec<String>,
371 pub time: f64,
372 #[serde(
373 serialize_with = "serialize_datetime_timestamp",
374 deserialize_with = "deserialize_datetime_timestamp"
375 )]
376 pub timestamp: NaiveDateTime,
377 pub country: String,
378}
379
380#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
381pub struct DRank {
382 pub rank: i64,
383 pub player: String,
384 pub time: f64,
385 #[serde(
386 serialize_with = "serialize_datetime_timestamp",
387 deserialize_with = "deserialize_datetime_timestamp"
388 )]
389 pub timestamp: NaiveDateTime,
390 pub country: String,
391}
392
393#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
394pub struct MaxFinish {
395 pub rank: i64,
396 pub player: String,
397 pub num: i64,
398 pub time: f64,
399 #[serde(
400 serialize_with = "serialize_datetime_timestamp",
401 deserialize_with = "deserialize_datetime_timestamp"
402 )]
403 pub min_timestamp: NaiveDateTime,
404 #[serde(
405 serialize_with = "serialize_datetime_timestamp",
406 deserialize_with = "deserialize_datetime_timestamp"
407 )]
408 pub max_timestamp: NaiveDateTime,
409}
410
411#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
412pub struct Points {
413 pub total: u64,
414 pub points: Option<u64>,
415 pub rank: Option<u64>,
416}
417
418#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
419pub struct Rank {
420 pub points: Option<u64>,
421 pub rank: Option<u64>,
422}
423
424#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
425pub struct FavoriteServer {
426 pub server: String,
427}
428
429#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
430pub struct FirstFinish {
431 #[serde(
432 serialize_with = "serialize_datetime_timestamp",
433 deserialize_with = "deserialize_datetime_timestamp"
434 )]
435 pub timestamp: NaiveDateTime,
436 pub map: String,
437 pub time: f64,
438}
439
440#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
441pub struct LastFinish {
442 #[serde(
443 serialize_with = "serialize_datetime_timestamp",
444 deserialize_with = "deserialize_datetime_timestamp"
445 )]
446 pub timestamp: NaiveDateTime,
447 pub map: String,
448 pub time: f64,
449 pub country: String,
450 #[serde(rename = "type")]
451 pub type_map: Option<String>,
452}
453
454#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
455pub struct FavoritePartner {
456 pub name: String,
457 pub finishes: i64,
458}
459
460#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
461#[serde(rename_all = "PascalCase")]
462pub struct Types {
463 pub novice: Type,
464 pub moderate: Type,
465 pub brutal: Type,
466 pub insane: Type,
467 pub dummy: Type,
468 #[serde(rename = "DDmaX.Easy")]
469 pub ddma_x_easy: Type,
470 #[serde(rename = "DDmaX.Next")]
471 pub ddma_x_next: Type,
472 #[serde(rename = "DDmaX.Pro")]
473 pub ddma_x_pro: Type,
474 #[serde(rename = "DDmaX.Nut")]
475 pub ddma_x_nut: Type,
476 pub oldschool: Type,
477 pub solo: Type,
478 pub race: Type,
479 pub fun: Type,
480}
481
482#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
483pub struct Type {
484 pub points: Points,
485 pub team_rank: Option<Rank>,
486 pub rank: Option<Rank>,
487 pub maps: HashMap<String, DDMap>,
488}
489
490#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
491pub struct DDMap {
492 pub points: i64,
493 pub total_finishes: i64,
494 pub finishes: i64,
495 pub team_rank: Option<i64>,
496 pub rank: Option<i64>,
497 pub time: Option<f64>,
498 pub first_finish: Option<f64>,
499}
500
501#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
502pub struct Activity {
503 pub date: String,
504 pub hours_played: i64,
505}
506
507fn default_location() -> String {
508 DEFAULT_LOCATION.to_string()
509}