1use std::fmt::Display;
2use std::str::FromStr;
3pub use steamid_ng::SteamID;
4use time::{Date, OffsetDateTime};
5
6#[cfg(feature = "serde")]
7mod serde_date {
8 use serde::de::Error;
9 use serde::ser::Error as _;
10 use serde::{Deserialize, Deserializer, Serialize, Serializer};
11 use time::format_description::FormatItem;
12 use time::macros::format_description;
13 use time::Date;
14
15 const DATE_FORMAT: &[FormatItem<'static>] = format_description!("[year]-[month]-[day]");
16
17 pub fn serialize<S: Serializer>(date: &Date, serializer: S) -> Result<S::Ok, S::Error> {
18 date.format(DATE_FORMAT)
19 .map_err(S::Error::custom)?
20 .serialize(serializer)
21 }
22
23 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Date, D::Error> {
24 let str = <&str>::deserialize(deserializer)?;
25 Date::parse(str, DATE_FORMAT).map_err(D::Error::custom)
26 }
27
28 pub mod opt {
29 use super::DATE_FORMAT;
30 use serde::de::Error;
31 use serde::ser::Error as _;
32 use serde::{Deserialize, Deserializer, Serialize, Serializer};
33 use time::Date;
34
35 pub fn serialize<S: Serializer>(
36 option: &Option<Date>,
37 serializer: S,
38 ) -> Result<S::Ok, S::Error> {
39 option
40 .map(|odt| odt.format(DATE_FORMAT))
41 .transpose()
42 .map_err(S::Error::custom)?
43 .serialize(serializer)
44 }
45
46 pub fn deserialize<'de, D: Deserializer<'de>>(
47 deserializer: D,
48 ) -> Result<Option<Date>, D::Error> {
49 let str = <Option<&str>>::deserialize(deserializer)?;
50 match str {
51 Some(str) => Date::parse(str, DATE_FORMAT)
52 .map_err(D::Error::custom)
53 .map(Some),
54 None => Ok(None),
55 }
56 }
57 }
58}
59
60#[derive(Debug, Clone)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62pub struct Player {
63 pub name: String,
64 pub avatar: String,
65 #[cfg_attr(feature = "serde", serde(with = "serde_steam_id_as_string"))]
66 pub steam_id: SteamID,
67 pub honors: Vec<Honors>,
68 pub teams: Vec<TeamMemberShip>,
69 pub favorite_classes: Vec<Class>,
70}
71
72#[derive(Debug, Clone)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
74pub struct Honors {
75 pub format: String,
76 pub season: String,
77 pub team: String,
78}
79
80#[derive(Debug, Clone)]
81#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82pub struct TeamMemberShip {
83 pub team: TeamRef,
84 pub league: String,
85 #[cfg_attr(feature = "serde", serde(with = "serde_date"))]
86 pub since: Date,
87}
88
89#[derive(Debug, Clone)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
91pub struct TeamRef {
92 pub name: String,
93 pub id: u32,
94}
95
96#[derive(Debug, Clone)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
98pub struct MembershipHistory {
99 pub format: String,
100 pub team: TeamRef,
101 pub division: String,
102 #[cfg_attr(feature = "serde", serde(with = "serde_date"))]
103 pub joined: Date,
104 #[cfg_attr(feature = "serde", serde(with = "serde_date::opt"))]
105 pub left: Option<Date>,
106}
107
108#[derive(Debug, Clone)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110pub struct Team {
111 pub name: String,
112 pub tag: String,
113 pub image: String,
114 pub format: String,
115 pub timezone: Option<String>,
116 pub steam_group: Option<String>,
117 pub division: String,
118 pub description: String,
119 pub titles: Vec<String>,
120 pub members: Vec<Membership>,
121 pub results: Vec<Record>,
122 pub name_changes: Vec<NameChange>,
123}
124
125#[derive(Debug, Clone)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
128pub enum Class {
129 Scout,
130 Soldier,
131 Pyro,
132 Demoman,
133 Heavy,
134 Medic,
135 Sniper,
136 Spy,
137}
138
139impl FromStr for Class {
140 type Err = ();
141
142 fn from_str(s: &str) -> Result<Self, Self::Err> {
143 match s {
144 "scout" => Ok(Class::Scout),
145 "soldier" => Ok(Class::Soldier),
146 "pyro" => Ok(Class::Pyro),
147 "demoman" => Ok(Class::Demoman),
148 "heavy" => Ok(Class::Heavy),
149 "medic" => Ok(Class::Medic),
150 "sniper" => Ok(Class::Sniper),
151 "spy" => Ok(Class::Spy),
152 _ => Err(()),
153 }
154 }
155}
156
157#[derive(Debug, Clone)]
158#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
159pub struct NameChange {
160 pub from_tag: String,
161 pub from: String,
162 pub to_tag: String,
163 pub to: String,
164 #[cfg_attr(feature = "serde", serde(with = "serde_date"))]
165 pub date: Date,
166}
167
168#[derive(Debug, Clone)]
169#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
170pub struct Membership {
171 pub name: String,
172 #[cfg_attr(feature = "serde", serde(with = "serde_steam_id_as_string"))]
173 pub steam_id: SteamID,
174 pub role: String,
175 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
176 pub since: OffsetDateTime,
177}
178
179#[derive(Debug, Clone)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
181pub struct Record {
182 pub season: u32,
183 pub division: String,
184 pub wins: u8,
185 pub losses: u8,
186}
187
188#[derive(Debug, Clone)]
189#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
190pub struct TeamRosterData {
191 pub steam_group: Option<String>,
192 pub history: Vec<RosterHistory>,
193}
194
195#[derive(Debug, Clone)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
197pub struct RosterHistory {
198 pub name: String,
199 #[cfg_attr(feature = "serde", serde(with = "serde_steam_id_as_string"))]
200 pub steam_id: SteamID,
201 #[cfg_attr(feature = "serde", serde(with = "serde_date"))]
202 pub joined: Date,
203 #[cfg_attr(feature = "serde", serde(with = "serde_date::opt"))]
204 pub left: Option<Date>,
205}
206
207#[derive(Debug, Clone)]
208#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
209pub struct TeamSeason {
210 pub season: u32,
211 pub matches: Vec<TeamSeasonMatch>,
212}
213
214#[derive(Debug, Clone)]
215#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
216pub struct TeamSeasonMatch {
217 pub division: String,
218 pub week: u8,
219 pub date: String,
220 pub side: String,
221 pub result: MatchResult,
222 pub map: String,
223}
224
225#[derive(Debug, Clone)]
226#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
227pub enum MatchResult {
228 Played {
229 id: u32,
230 opponent: TeamRef,
231 score: u8,
232 score_opponent: u8,
233 match_points: f32,
234 match_points_opponent: f32,
235 },
236 Pending {
237 id: u32,
238 opponent: TeamRef,
239 score: u8,
240 score_opponent: u8,
241 },
242 ByeWeek,
243}
244
245#[derive(Debug, Clone)]
246#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
247pub struct Seasons {
248 pub mode: String,
249 pub seasons: Vec<Season>,
250}
251
252#[derive(Debug, Clone)]
253#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
254pub struct Season {
255 pub id: String,
256 pub name: String,
257}
258
259#[derive(Debug, Clone)]
260#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
261pub struct MatchInfo {
262 pub comment: Option<String>,
263 pub comment_author: Option<String>,
264 pub team_home: TeamRef,
265 pub team_away: TeamRef,
266 pub score_home: u8,
267 pub score_away: u8,
268}
269
270#[derive(Debug, Clone, Copy)]
271pub enum GameMode {
272 Highlander,
273 Sixes,
274 Fours,
275 Ultiduo,
276}
277
278impl FromStr for GameMode {
279 type Err = ();
280
281 fn from_str(s: &str) -> Result<Self, Self::Err> {
282 match s {
283 "9v9" => Ok(GameMode::Highlander),
284 "6v6" => Ok(GameMode::Sixes),
285 "4v4" => Ok(GameMode::Fours),
286 "2v2" => Ok(GameMode::Ultiduo),
287 _ => Err(()),
288 }
289 }
290}
291
292impl GameMode {
293 pub fn letter(&self) -> char {
294 match self {
295 GameMode::Highlander => 'h',
296 GameMode::Sixes => '6',
297 GameMode::Fours => '4',
298 GameMode::Ultiduo => '2',
299 }
300 }
301
302 fn as_str(&self) -> &'static str {
303 match self {
304 GameMode::Highlander => "9v9",
305 GameMode::Sixes => "6v6",
306 GameMode::Fours => "4v4",
307 GameMode::Ultiduo => "2v2",
308 }
309 }
310}
311
312impl Display for GameMode {
313 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314 f.write_str(self.as_str())
315 }
316}
317
318#[derive(Debug, Clone)]
319#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
320pub struct Transaction {
321 pub name: String,
322 #[cfg_attr(feature = "serde", serde(with = "serde_steam_id_as_string"))]
323 pub steam_id: SteamID,
324 pub action: TransactionAction,
325 pub team: TeamRef,
326}
327
328#[derive(Debug, Clone)]
329#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
330pub enum TransactionAction {
331 Joined,
332 Left,
333}
334
335pub struct MallFormedTransaction {
337 pub text: String,
338}
339
340impl FromStr for TransactionAction {
341 type Err = MallFormedTransaction;
342
343 fn from_str(s: &str) -> Result<Self, Self::Err> {
344 match s {
345 "Joined" => Ok(TransactionAction::Joined),
346 "Left" => Ok(TransactionAction::Left),
347 _ => Err(MallFormedTransaction {
348 text: s.to_string(),
349 }),
350 }
351 }
352}
353
354#[derive(Debug, Clone)]
355#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
356pub struct MapHistory {
357 pub current: CurrentSeasonMapList,
358 pub previous: Vec<PreviousSeasonMapList>,
359}
360
361#[derive(Debug, Clone)]
362#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
363pub struct CurrentSeasonMapList {
364 pub season: u8,
365 pub maps: Vec<CurrentSeasonMap>,
366}
367#[derive(Debug, Clone)]
368#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
369pub struct PreviousSeasonMapList {
370 pub season: u8,
371 pub maps: Vec<PreviousSeasonMap>,
372}
373
374#[derive(Debug, Clone)]
375#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
376pub struct CurrentSeasonMap {
377 pub week: u8,
378 pub map: String,
379 pub date: String,
380 pub na_date: Option<String>,
381}
382
383#[derive(Debug, Clone)]
384#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
385pub struct PreviousSeasonMap {
386 pub week: u8,
387 pub map: String,
388 #[cfg_attr(feature = "serde", serde(with = "serde_date"))]
389 pub date: Date,
390}
391
392#[cfg(feature = "serde")]
393mod serde_steam_id_as_string {
394 use serde::{Deserialize, Deserializer, Serialize, Serializer};
395 use steamid_ng::SteamID;
396
397 pub fn serialize<S: Serializer>(steam_id: &SteamID, serializer: S) -> Result<S::Ok, S::Error> {
398 let id = u64::from(*steam_id);
399 format!("{id}").serialize(serializer)
400 }
401
402 #[allow(dead_code)]
403 pub fn deserialize<'de, D>(deserializer: D) -> Result<SteamID, D::Error>
404 where
405 D: Deserializer<'de>,
406 {
407 let id = u64::deserialize(deserializer)?;
408 Ok(SteamID::from(id))
409 }
410}