ddapi_rs/scheme/
ddnet.rs

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}