1use rand::prelude::*;
2use rand::rng;
3use reqwest::{Client, Error};
4use serde::Deserialize;
5use std::iter::Cycle;
6use std::net::SocketAddr;
7use std::sync::{Arc, Mutex};
8use std::vec::IntoIter;
9use thiserror::Error;
10use tracing::debug;
11
12#[derive(Debug, Error)]
13#[non_exhaustive]
14pub enum ServerDiscoveryError {
15 #[error("Failed send discovery request: {0:#}")]
16 Network(reqwest::Error),
17 #[error("steam returned an empty server list")]
18 NoServers,
19 #[error("steam returned an empty websocket server list")]
20 NoWsServers,
21}
22
23impl From<reqwest::Error> for ServerDiscoveryError {
24 fn from(value: Error) -> Self {
25 ServerDiscoveryError::Network(value)
26 }
27}
28
29#[derive(Default, Clone, Debug)]
31pub struct DiscoverOptions {
32 web_client: Option<Client>,
33 cell: u8,
36}
37
38impl DiscoverOptions {
39 pub fn with_web_client(self, web_client: Client) -> Self {
41 DiscoverOptions {
42 web_client: Some(web_client),
43 ..self
44 }
45 }
46
47 pub fn with_cell(self, cell: u8) -> Self {
49 DiscoverOptions { cell, ..self }
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct ServerList {
56 tcp_count: usize,
57 tcp_servers: Arc<Mutex<Cycle<IntoIter<SocketAddr>>>>,
58 ws_count: usize,
59 ws_servers: Arc<Mutex<Cycle<IntoIter<String>>>>,
60}
61
62impl ServerList {
63 pub fn new(
65 tcp_servers: Vec<SocketAddr>,
66 ws_servers: Vec<String>,
67 ) -> Result<Self, ServerDiscoveryError> {
68 if tcp_servers.is_empty() {
69 return Err(ServerDiscoveryError::NoServers);
70 }
71 if ws_servers.is_empty() {
72 return Err(ServerDiscoveryError::NoWsServers);
73 }
74
75 Ok(ServerList {
76 tcp_count: tcp_servers.len(),
77 ws_count: ws_servers.len(),
78 tcp_servers: Arc::new(Mutex::new(tcp_servers.into_iter().cycle())),
79 ws_servers: Arc::new(Mutex::new(ws_servers.into_iter().cycle())),
80 })
81 }
82
83 pub async fn discover() -> Result<ServerList, ServerDiscoveryError> {
85 Self::discover_with(DiscoverOptions::default()).await
86 }
87
88 pub async fn discover_with(
90 options: DiscoverOptions,
91 ) -> Result<ServerList, ServerDiscoveryError> {
92 let client = options.web_client.unwrap_or_default();
93 let cell = options.cell;
94
95 let response: ServerListResponse = client
96 .get(format!(
97 "https://api.steampowered.com/ISteamDirectory/GetCMList/v1/?cellid={cell}"
98 ))
99 .send()
100 .await?
101 .json()
102 .await?;
103 response.try_into()
104 }
105
106 pub fn pick(&self) -> SocketAddr {
111 let addr = self.tcp_servers.lock().unwrap().next().unwrap();
115 debug!(addr = ?addr, "picked server from list");
116 addr
117 }
118
119 pub fn pick_ws(&self) -> String {
124 let addr = self.ws_servers.lock().unwrap().next().unwrap();
126 debug!(addr = ?addr, "picked websocket server from list");
127 format!("wss://{addr}/cmsocket/")
128 }
129
130 pub fn tcp_servers(&self) -> Vec<SocketAddr> {
131 let mut iter = self.tcp_servers.lock().unwrap();
132 take_from_iter(&mut *iter, self.tcp_count)
133 }
134
135 pub fn ws_servers(&self) -> Vec<String> {
136 let mut iter = self.ws_servers.lock().unwrap();
137 take_from_iter(&mut *iter, self.ws_count)
138 }
139}
140
141fn take_from_iter<T, I: Iterator<Item = T>>(iter: &mut I, count: usize) -> Vec<T> {
142 let mut result = Vec::with_capacity(count);
143 for _ in 0..count {
144 if let Some(item) = iter.next() {
145 result.push(item)
146 }
147 }
148 result
149}
150
151#[test]
152fn test_save_servers() {
153 use std::net::{IpAddr, Ipv4Addr};
154
155 let socket1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 1234);
156 let socket2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 2345);
157
158 let ws1 = String::from("server1:1234");
159 let ws2 = String::from("server2");
160 let ws3 = String::from("server3");
161
162 let list = ServerList::new(
163 vec![socket1, socket2],
164 vec![ws1.clone(), ws2.clone(), ws3.clone()],
165 )
166 .unwrap();
167
168 assert_eq!(vec![socket1, socket2], list.tcp_servers());
169 assert_eq!(
170 vec![ws1.clone(), ws2.clone(), ws3.clone()],
171 list.ws_servers()
172 );
173
174 let _ = list.pick();
175 let _ = list.pick_ws();
176 let _ = list.pick_ws();
177 let _ = list.pick_ws();
178
179 assert_eq!(vec![socket2, socket1], list.tcp_servers());
180 assert_eq!(
181 vec![ws1.clone(), ws2.clone(), ws3.clone()],
182 list.ws_servers()
183 );
184}
185
186impl TryFrom<ServerListResponse> for ServerList {
187 type Error = ServerDiscoveryError;
188
189 fn try_from(value: ServerListResponse) -> Result<Self, Self::Error> {
190 let (mut servers, mut ws_servers) = (
191 value.response.server_list,
192 value.response.server_list_websockets,
193 );
194 servers.shuffle(&mut rng());
195 ws_servers.shuffle(&mut rng());
196
197 ServerList::new(servers, ws_servers)
198 }
199}
200
201#[derive(Debug, Deserialize)]
202struct ServerListResponse {
203 response: ServerListResponseInner,
204}
205
206#[derive(Debug, Deserialize)]
207struct ServerListResponseInner {
208 #[serde(rename = "serverlist")]
209 server_list: Vec<SocketAddr>,
210 #[serde(rename = "serverlist_websockets")]
211 server_list_websockets: Vec<String>,
212}