serverstat/
qtv.rs

1use quake_serverinfo::Settings;
2use quake_text::bytestr::to_unicode;
3
4use crate::client::QuakeClient;
5use crate::server::QuakeServer;
6use crate::tokenize;
7
8use crate::hostport::Hostport;
9#[cfg(feature = "json")]
10use serde::{Deserialize, Serialize};
11
12#[derive(Clone, Debug, Default, Eq, PartialEq)]
13#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
14pub struct QtvServer {
15    pub settings: QtvSettings,
16    pub clients: Vec<QtvClient>,
17}
18
19impl From<&QuakeServer> for QtvServer {
20    fn from(server: &QuakeServer) -> Self {
21        let settings = QtvSettings::from(&server.settings);
22        let clients = server.clients.iter().map(QtvClient::from).collect();
23        Self { settings, clients }
24    }
25}
26
27#[derive(Clone, Debug, Default, Eq, PartialEq)]
28#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
29pub struct QtvSettings {
30    pub hostname: String,
31    pub maxclients: u32,
32    pub version: String,
33}
34
35impl From<&Settings> for QtvSettings {
36    fn from(settings: &Settings) -> Self {
37        Self {
38            hostname: settings.hostname.clone().unwrap_or_default(),
39            maxclients: settings.maxclients.unwrap_or_default() as u32,
40            version: settings.version.clone().unwrap_or_default(),
41        }
42    }
43}
44
45#[derive(Clone, Debug, Default, Eq, PartialEq)]
46#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
47pub struct QtvClient {
48    pub id: u32,
49    pub time: u32,
50    pub name: String,
51}
52
53impl From<&QuakeClient> for QtvClient {
54    fn from(client: &QuakeClient) -> Self {
55        Self {
56            id: client.id,
57            time: client.time,
58            name: client.name.clone(),
59        }
60    }
61}
62
63#[derive(Clone, Debug, Default, Eq, PartialEq)]
64#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
65pub struct QtvStream {
66    pub id: u32,
67    pub name: String,
68    pub number: u32,
69    pub address: Hostport,
70    pub client_count: u32,
71    pub client_names: Vec<String>,
72}
73
74impl QtvStream {
75    pub fn url(&self) -> String {
76        format!("{}@{}", self.number, self.address)
77    }
78}
79
80impl TryFrom<&[u8]> for QtvStream {
81    type Error = anyhow::Error;
82
83    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
84        let parts: Vec<String> = tokenize::tokenize(to_unicode(bytes).as_str());
85        let id: u32 = parts[1].parse()?;
86        let name = parts[2].to_string();
87        let url = parts[3].to_string();
88        let (number, address) = match url.split_once('@') {
89            Some((number_str, hostport)) => {
90                let number = number_str.parse::<u32>().unwrap_or_default();
91                (number, hostport.to_string())
92            }
93            None => (0, url.clone()),
94        };
95        let client_count: u32 = parts[4].parse()?;
96        let address = Hostport::try_from(address.as_str())?;
97
98        Ok(Self {
99            id,
100            name,
101            number,
102            address,
103            client_count,
104            client_names: vec![],
105        })
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use anyhow::Result;
113    use pretty_assertions::assert_eq;
114    use std::time::Duration;
115
116    #[tokio::test]
117    async fn test_from_gameserver() -> Result<()> {
118        let server =
119            QuakeServer::try_from_address("quake.se:28000", Duration::from_secs_f32(0.5)).await?;
120        assert_eq!(
121            QtvServer::from(&server).settings.hostname,
122            "QUAKE.SE KTX Qtv"
123        );
124        Ok(())
125    }
126
127    #[test]
128    fn test_qtv_stream_methods() {
129        let stream = QtvStream {
130            number: 2,
131            address: Hostport {
132                host: "dm6.uk".to_string(),
133                port: 28000,
134            },
135            ..Default::default()
136        };
137        assert_eq!(stream.url(), "2@dm6.uk:28000".to_string());
138    }
139
140    #[test]
141    fn test_qtv_stream_from_bytes() -> Result<()> {
142        assert_eq!(
143            QtvStream::try_from(br#"nqtv 1 "dm6.uk Qtv (7)" "7@dm6.uk:28000" 4"#.as_ref())?,
144            QtvStream {
145                id: 1,
146                name: "dm6.uk Qtv (7)".to_string(),
147                number: 7,
148                address: Hostport {
149                    host: "dm6.uk".to_string(),
150                    port: 28000,
151                },
152                client_count: 4,
153                client_names: vec![],
154            }
155        );
156        Ok(())
157    }
158
159    #[test]
160    fn test_from_quakeclient() {
161        assert_eq!(
162            QtvClient::from(&QuakeClient {
163                id: 7,
164                name: "XantoM".to_string(),
165                team: "f0m".to_string(),
166                frags: 12,
167                ping: 25,
168                time: 15,
169                top_color: 4,
170                bottom_color: 2,
171                skin: "XantoM".to_string(),
172                auth_cc: "xtm".to_string(),
173                is_spectator: false,
174                is_bot: false,
175            }),
176            QtvClient {
177                id: 7,
178                name: "XantoM".to_string(),
179                time: 15,
180            }
181        );
182    }
183}