1use crate::prelude::Addr;
2use crate::prelude::{addr_serialization, Protocol};
3use crate::scheme::DDNET_BASE_URL;
4use serde::{Deserialize, Serialize};
5use std::collections::{HashMap, HashSet};
6
7fn default_location() -> String {
8 "unknown".to_string()
9}
10
11fn deserialize_optional_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
14where
15 D: serde::Deserializer<'de>,
16{
17 use serde::de::Error;
18 let value = Option::<serde_json::Value>::deserialize(deserializer)?;
19 Ok(match value {
20 None => None,
21 Some(serde_json::Value::String(s)) => Some(s),
22 Some(serde_json::Value::Number(n)) => Some(n.to_string()),
23 Some(other) => {
24 return Err(D::Error::custom(format!(
25 "expected string or number for country, got `{other}`"
26 )))
27 }
28 })
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum MasterServer {
33 One = 1,
34 Two = 2,
35 Three = 3,
36 Four = 4,
37}
38
39impl MasterServer {
40 #[must_use]
41 pub fn get_index(&self) -> i32 {
42 *self as i32
43 }
44
45 #[must_use]
46 pub fn api(&self) -> String {
47 format!(
48 "https://master{}.{}/ddnet/15/servers.json",
49 self.get_index(),
50 DDNET_BASE_URL
51 )
52 }
53}
54
55#[derive(Default, Debug, Clone)]
56pub struct ClanCount {
57 pub name: String,
58 pub count: usize,
59}
60
61#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub struct Master {
64 pub communities: Vec<Community>,
65 pub servers: Vec<Server>,
66}
67
68#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub struct Community {
70 pub id: String,
71 pub name: String,
72 pub has_finishes: bool,
73 pub icon: Icon,
74 pub contact_urls: Vec<String>,
75}
76
77#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
78pub struct Icon {
79 pub sha256: String,
80 pub url: String,
81}
82
83impl Master {
84 #[must_use]
85 pub fn api(master: MasterServer) -> String {
86 master.api()
87 }
88
89 #[must_use]
90 pub fn count_clients(&self) -> usize {
91 self.servers.iter().map(|s| s.info.clients.len()).sum()
92 }
93
94 #[must_use]
95 pub fn get_clans(&self) -> Vec<ClanCount> {
96 self.get_filtered_clans(None)
97 }
98
99 #[must_use]
100 pub fn get_filtered_clans(&self, filters: Option<Vec<&str>>) -> Vec<ClanCount> {
101 if self.servers.is_empty() {
102 return Vec::new();
103 }
104
105 let filter_set: HashSet<&str> = filters.unwrap_or_default().into_iter().collect();
106
107 let mut clan_counts = HashMap::new();
108
109 for server in &self.servers {
110 for client in &server.info.clients {
111 if !client.clan.is_empty() && !filter_set.contains(client.clan.as_str()) {
112 *clan_counts.entry(client.clan.clone()).or_insert(0) += 1;
113 }
114 }
115 }
116
117 let mut result: Vec<ClanCount> = clan_counts
118 .into_iter()
119 .map(|(name, count)| ClanCount { name, count })
120 .collect();
121
122 result.sort_by_key(|x| std::cmp::Reverse(x.count));
123 result
124 }
125}
126
127#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
128pub struct Server {
129 #[serde(with = "addr_serialization")]
130 pub addresses: Vec<Addr>,
131 pub community: Option<String>,
132 #[serde(default = "default_location")]
133 pub location: String,
134 pub info: Info,
135}
136
137impl Server {
138 #[must_use]
139 pub fn count_client(&self) -> usize {
140 self.info.clients.len()
141 }
142
143 #[must_use]
144 pub fn ipv4_addresses(&self) -> Vec<&Addr> {
145 self.addresses
146 .iter()
147 .filter(|addr| addr.ip.is_ipv4())
148 .collect()
149 }
150
151 #[must_use]
152 pub fn ipv6_addresses(&self) -> Vec<&Addr> {
153 self.addresses
154 .iter()
155 .filter(|addr| addr.ip.is_ipv6())
156 .collect()
157 }
158
159 #[must_use]
160 pub fn addresses_by_protocol(&self, protocol: Protocol) -> Vec<&Addr> {
161 self.addresses
162 .iter()
163 .filter(|addr| addr.protocol == protocol)
164 .collect()
165 }
166}
167
168#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
169pub struct Info {
170 pub max_clients: i64,
171 pub max_players: i64,
172 #[serde(default)]
173 pub passworded: bool,
174 #[serde(rename = "game_type")]
175 pub gametype: String,
176 pub name: String,
177 pub map: IMap,
178 pub version: String,
179 #[serde(default)]
180 pub clients: Vec<Client>,
181 #[serde(default)]
182 pub requires_login: bool,
183 #[serde(default)]
184 pub client_score_kind: Option<String>,
185 #[serde(default, deserialize_with = "deserialize_optional_string")]
186 pub country: Option<String>,
187 #[serde(default)]
188 pub flags: Option<Vec<String>>,
189 #[serde(default)]
190 pub flag: Option<i64>,
191 #[serde(default)]
192 pub identity_key: Option<String>,
193}
194
195#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
196pub struct IMap {
197 pub name: String,
198 pub sha256: Option<String>,
199 pub size: Option<i64>,
200 #[serde(default)]
201 pub url: Option<String>,
202 #[serde(default, deserialize_with = "deserialize_optional_string")]
203 pub tw_crc: Option<String>,
204}
205
206#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
207pub struct Client {
208 pub name: String,
209 pub clan: String,
210 pub country: i32,
211 pub score: i64,
212 #[serde(default)]
213 pub is_player: bool,
214 pub skin: Option<Skin>,
215 #[serde(default)]
216 pub afk: bool,
217 #[serde(default)]
218 pub team: i64,
219}
220
221#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
222pub struct Skin {
223 pub name: Option<String>,
224 pub color_body: Option<i64>,
225 pub color_feet: Option<i64>,
226 #[serde(default)]
227 pub body: Option<SkinPart>,
228 #[serde(default)]
229 pub marking: Option<SkinPart>,
230 #[serde(default)]
231 pub decoration: Option<SkinPart>,
232 #[serde(default)]
233 pub eyes: Option<SkinPart>,
234 #[serde(default)]
235 pub feet: Option<SkinPart>,
236 #[serde(default)]
237 pub hands: Option<SkinPart>,
238}
239
240#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
241pub struct SkinPart {
242 pub name: String,
243 #[serde(default)]
244 pub color: Option<i64>,
245}