6e87bad1633f1fbe0a378ecaa9914858d1d1417d
4 use std
::net
::{SocketAddr
, SocketAddrV4
, ToSocketAddrs
};
6 use serde
::{Serialize
, Serializer
};
8 use crate::HLQueryError
::{IOError
,A2SError
};
10 macro_rules
! write_datapoint
{
11 ($formatter
:expr
,$pad
:expr
,$caption
:expr
,$var
:expr
) => {
12 writeln
!($formatter
, "{:<pad$} {}", $caption
, $var
, pad
= $pad
)
16 macro_rules
! implement_displaydebug
{
17 ($foreigntype
:path
,$localtype
:ident
) => {
18 struct $localtype
<'a
>(&'a $foreigntype
);
20 impl Display
for $localtype
<'_
> {
21 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
22 write
!(f
, "{:?}", self.0)
28 implement_displaydebug
!(a2s
::info
::ServerOS
, ServerOS
);
29 implement_displaydebug
!(a2s
::info
::ServerType
, ServerType
);
30 implement_displaydebug
!(Option
<a2s
::info
::TheShip
>, TheShip
);
31 implement_displaydebug
!(Option
<a2s
::info
::SourceTVInfo
>, SourceTVInfo
);
32 implement_displaydebug
!(Option
<a2s
::players
::TheShipPlayer
>, TheShipPlayer
);
35 #[command(name = "HLQuery")]
36 #[command(author = "MegaBrutal")]
38 #[command(about = "Query Half-Life servers", long_about = None)]
40 /// Print output in JSON format
44 /// Print output in Rust debug format
48 /// Pretty-print JSON or Rust objects
52 addresses
: Vec
<String
>
55 #[derive(Debug, Serialize)]
56 struct HLQueryResult
{
57 address
: SocketAddrV4
,
58 info
: Result
<a2s
::info
::Info
, HLQueryError
>,
59 rules
: Result
<Vec
<a2s
::rules
::Rule
>, HLQueryError
>,
60 players
: Result
<Vec
<a2s
::players
::Player
>, HLQueryError
>
64 fn new(a2s_client
: &A2SClient
, server
: SocketAddrV4
) -> Self {
67 info
: a2s_client
.in
fo
(server
).map_err(From
::from
),
68 rules
: a2s_client
.rules(server
).map_err(From
::from
),
69 players
: a2s_client
.players(server
).map_err(From
::from
)
74 const PAD_WIDTH
: usize = 25;
76 impl Display
for HLQueryResult
{
77 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
78 write_datapoint
!(f
, PAD_WIDTH
, "Address:", self.address
)?
;
81 writeln
!(f
, "\nServer info:")?
;
82 write_datapoint
!(f
, PAD_WIDTH
, "Server name:", info
.name
)?
;
83 write_datapoint
!(f
, PAD_WIDTH
, "Game:", info
.game
)?
;
84 write_datapoint
!(f
, PAD_WIDTH
, "Folder:", info
.folder
)?
;
85 write_datapoint
!(f
, PAD_WIDTH
, "Current map:", info
.map
)?
;
86 write_datapoint
!(f
, PAD_WIDTH
, "Protocol version:", info
.protocol
)?
;
87 write_datapoint
!(f
, PAD_WIDTH
, "Steam App ID:", info
.app_id
)?
;
88 write_datapoint
!(f
, PAD_WIDTH
, "Number of players:", format
!("{}/{}", info
.players
, info
.max_players
))?
;
89 write_datapoint
!(f
, PAD_WIDTH
, "Number of bots:", info
.bots
)?
;
90 write_datapoint
!(f
, PAD_WIDTH
, "Server type:", ServerType(&info
.server_type
))?
;
91 write_datapoint
!(f
, PAD_WIDTH
, "Server OS:", ServerOS(&info
.server_os
))?
;
92 write_datapoint
!(f
, PAD_WIDTH
, "Password protected:", if info
.visibility
{ "Private" } else { "Public" })?
;
93 write_datapoint
!(f
, PAD_WIDTH
, "VAC enabled:", info
.vac
)?
;
94 write_datapoint
!(f
, PAD_WIDTH
, "The Ship mode:", TheShip(&info
.the_ship
))?
;
95 write_datapoint
!(f
, PAD_WIDTH
, "Server version:", info
.version
)?
;
96 write_datapoint
!(f
, PAD_WIDTH
, "Extra Data Flag:", info
.edf
)?
;
97 write_datapoint
!(f
, PAD_WIDTH
, "Server port:", info
.extended_server_info
.port
.map_or("Unknown".to_string(), |i
| i
.to_string()))?
;
98 write_datapoint
!(f
, PAD_WIDTH
, "Steam ID:", info
.extended_server_info
.steam_id
.map_or("Unknown".to_string(), |i
| i
.to_string()))?
;
99 write_datapoint
!(f
, PAD_WIDTH
, "Keywords:", info
.extended_server_info
.keywords
.clone().unwrap
_or
("Unknown".to_string()))?
;
100 write_datapoint
!(f
, PAD_WIDTH
, "Game ID:", info
.extended_server_info
.game_id
.map_or("Unknown".to_string(), |i
| i
.to_string()))?
;
101 write_datapoint
!(f
, PAD_WIDTH
, "Source TV port:", SourceTVInfo(&info
.source_tv
))?
;
104 writeln
!(f
, "Failed to query server info:\t{}", e
)?
109 writeln
!(f
, "\nGame rules (CVARs):")?
;
111 write_datapoint
!(f
, PAD_WIDTH
, rule
.name
, format
!("\"{}\"", rule
.value
))?
;
115 writeln
!(f
, "Failed to query game rules:\t{}", e
)?
118 match &self.players
{
120 writeln
!(f
, "\nPlayers:")?
;
121 writeln
!(f
, "\t{:>3} players online.", players
.len())?
;
122 for player
in players
{
123 writeln
!(f
, "{}: {:<30} {:>4} {:>10} {}",
128 TheShipPlayer(&player
.the_ship
))?
;
133 writeln
!(f
, "Failed to query players:\t{}", e
)
138 #[derive(Debug, Serialize)]
141 result
: Result
<Vec
<HLQueryResult
>, HLQueryError
>
145 fn new
<S
: Into
<String
>>(input
: S
, result
: Result
<Vec
<HLQueryResult
>, HLQueryError
>) -> Self {
146 let input
= input
.into
();
147 Self { input
, result
}
151 impl Display
for HLQuery
{
152 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
153 write_datapoint
!(f
, PAD_WIDTH
, "Input:", self.inp
ut
)?
;
156 write_datapoint
!(f
, PAD_WIDTH
, "Number of addresses:", format
!("{}\n", v
.len()))?
;
158 writeln
!(f
, "{r}\n")?
;
163 writeln
!(f
, "Failed to execute query:\t{e}\n")
170 IOError(std
::io
::Error
),
171 A2SError(a2s
::errors
::Error
)
174 impl Display
for HLQueryError
{
175 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
177 IOError(e
) => write
!(f
, "{:?}", e
),
178 A2SError(e
) => write
!(f
, "{:?}", e
)
183 impl Error
for HLQueryError
{}
185 impl Serialize
for HLQueryError
{
186 fn serialize
<S
>(&self, serializer
: S
) -> Result
<S
::Ok
, S
::Error
>
190 serializer
.serialize_str(&format
!("{self}"))
194 impl From
<std
::io
::Error
> for HLQueryError
{
195 fn from(e
: std
::io
::Error
) -> Self {
200 impl From
<a2s
::errors
::Error
> for HLQueryError
{
201 fn from(e
: a2s
::errors
::Error
) -> Self {
208 let cli
= Cli
::parse();
210 let client
= A2SClient
::new().unwrap
();
211 let query_results
: Vec
<HLQuery
> = cli
.addresses
.iter
()
213 let addresses
= arg
.to_socket_addrs();
216 .map(|lookup_result
| match lookup_result
{
217 (input
, Ok(iter_addr
)) => {
218 (input
, Ok(iter_addr
.filter
_map
(|sa
| match sa
{
219 SocketAddr
::V4(sa4
) => Some(sa4
),
223 (input
, Err(e
)) => (input
, Err(HLQueryError
::IOError(e
)))
225 .map(|(input
, address_group
)| HLQuery
::new(input
, address_group
.map(
226 |addresses
| addresses
.map(|addr
| HLQueryResult
::new(&client
, addr
)).collect())))
231 println
!("{}", serde_json
::to_string_pretty(&query_results
).unwrap
());
234 println
!("{}", serde_json
::to_string(&query_results
).unwrap
());
239 println
!("{:#?}", query_results
);
242 println
!("{:?}", query_results
);
246 for query
in query_results
{
247 println
!("{}", query
);