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)]
30pub struct DiscoverOptions {
31 web_client: Option<Client>,
32 cell: u8,
35}
36
37impl DiscoverOptions {
38 pub fn with_web_client(self, web_client: Client) -> Self {
39 DiscoverOptions {
40 web_client: Some(web_client),
41 ..self
42 }
43 }
44
45 pub fn with_cell(self, cell: u8) -> Self {
46 DiscoverOptions { cell, ..self }
47 }
48}
49
50#[derive(Debug, Clone)]
51pub struct ServerList {
52 servers: Arc<Mutex<Cycle<IntoIter<SocketAddr>>>>,
53 ws_servers: Arc<Mutex<Cycle<IntoIter<String>>>>,
54}
55
56impl ServerList {
57 pub async fn discover() -> Result<ServerList, ServerDiscoveryError> {
58 Self::discover_with(DiscoverOptions::default()).await
59 }
60
61 pub async fn discover_with(
62 options: DiscoverOptions,
63 ) -> Result<ServerList, ServerDiscoveryError> {
64 let client = options.web_client.unwrap_or_default();
65 let cell = options.cell;
66
67 let response: ServerListResponse = client
68 .get(format!(
69 "https://api.steampowered.com/ISteamDirectory/GetCMList/v1/?cellid={cell}"
70 ))
71 .send()
72 .await?
73 .json()
74 .await?;
75 if response.response.server_list.is_empty() {
76 return Err(ServerDiscoveryError::NoServers);
77 }
78 if response.response.server_list.is_empty() {
79 return Err(ServerDiscoveryError::NoWsServers);
80 }
81 Ok(response.into())
82 }
83
84 pub fn pick(&self) -> SocketAddr {
89 let addr = self.servers.lock().unwrap().next().unwrap();
93 debug!(addr = ?addr, "picked server from list");
94 addr
95 }
96
97 pub fn pick_ws(&self) -> String {
102 let addr = self.ws_servers.lock().unwrap().next().unwrap();
104 debug!(addr = ?addr, "picked websocket server from list");
105 format!("wss://{addr}/cmsocket/")
106 }
107}
108
109impl From<ServerListResponse> for ServerList {
110 fn from(value: ServerListResponse) -> Self {
111 let (mut servers, mut ws_servers) = (
112 value.response.server_list,
113 value.response.server_list_websockets,
114 );
115 servers.shuffle(&mut rng());
116 ws_servers.shuffle(&mut rng());
117
118 ServerList {
119 servers: Arc::new(Mutex::new(servers.into_iter().cycle())),
120 ws_servers: Arc::new(Mutex::new(ws_servers.into_iter().cycle())),
121 }
122 }
123}
124
125#[derive(Debug, Deserialize)]
126struct ServerListResponse {
127 response: ServerListResponseInner,
128}
129
130#[derive(Debug, Deserialize)]
131struct ServerListResponseInner {
132 #[serde(rename = "serverlist")]
133 server_list: Vec<SocketAddr>,
134 #[serde(rename = "serverlist_websockets")]
135 server_list_websockets: Vec<String>,
136}