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;
75 const SEP_WIDTH
: usize = 60;
77 impl Display
for HLQueryResult
{
78 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
79 writeln
!(f
, "{}", "=".repeat(SEP_WIDTH
))?
;
80 write_datapoint
!(f
, PAD_WIDTH
, "Address:", self.address
)?
;
81 writeln
!(f
, "{}", "=".repeat(SEP_WIDTH
))?
;
85 writeln
!(f
, "\n* Server info: *\n")?
;
86 write_datapoint
!(f
, PAD_WIDTH
, "Server name:", info
.name
)?
;
87 write_datapoint
!(f
, PAD_WIDTH
, "Game:", info
.game
)?
;
88 write_datapoint
!(f
, PAD_WIDTH
, "Folder:", info
.folder
)?
;
89 write_datapoint
!(f
, PAD_WIDTH
, "Current map:", info
.map
)?
;
90 write_datapoint
!(f
, PAD_WIDTH
, "Protocol version:", info
.protocol
)?
;
91 write_datapoint
!(f
, PAD_WIDTH
, "Steam App ID:", info
.app_id
)?
;
92 write_datapoint
!(f
, PAD_WIDTH
, "Number of players:", format
!("{}/{}", info
.players
, info
.max_players
))?
;
93 write_datapoint
!(f
, PAD_WIDTH
, "Number of bots:", info
.bots
)?
;
94 write_datapoint
!(f
, PAD_WIDTH
, "Server type:", ServerType(&info
.server_type
))?
;
95 write_datapoint
!(f
, PAD_WIDTH
, "Server OS:", ServerOS(&info
.server_os
))?
;
96 write_datapoint
!(f
, PAD_WIDTH
, "Password protected:", if info
.visibility
{ "Private" } else { "Public" })?
;
97 write_datapoint
!(f
, PAD_WIDTH
, "VAC enabled:", info
.vac
)?
;
98 write_datapoint
!(f
, PAD_WIDTH
, "The Ship mode:", TheShip(&info
.the_ship
))?
;
99 write_datapoint
!(f
, PAD_WIDTH
, "Server version:", info
.version
)?
;
100 write_datapoint
!(f
, PAD_WIDTH
, "Extra Data Flag:", info
.edf
)?
;
101 write_datapoint
!(f
, PAD_WIDTH
, "Server port:", info
.extended_server_info
.port
.map_or("Unknown".to_string(), |i
| i
.to_string()))?
;
102 write_datapoint
!(f
, PAD_WIDTH
, "Steam ID:", info
.extended_server_info
.steam_id
.map_or("Unknown".to_string(), |i
| i
.to_string()))?
;
103 write_datapoint
!(f
, PAD_WIDTH
, "Keywords:", info
.extended_server_info
.keywords
.clone().unwrap
_or
("Unknown".to_string()))?
;
104 write_datapoint
!(f
, PAD_WIDTH
, "Game ID:", info
.extended_server_info
.game_id
.map_or("Unknown".to_string(), |i
| i
.to_string()))?
;
105 write_datapoint
!(f
, PAD_WIDTH
, "Source TV port:", SourceTVInfo(&info
.source_tv
))?
;
108 writeln
!(f
, "\nFailed to query server info:\t{}", e
)?
113 writeln
!(f
, "\n\n* Game rules (CVARs): *\n")?
;
115 write_datapoint
!(f
, PAD_WIDTH
, rule
.name
, format
!("\"{}\"", rule
.value
))?
;
119 writeln
!(f
, "\nFailed to query game rules:\t{}", e
)?
122 match &self.players
{
124 writeln
!(f
, "\n\n* Players: *")?
;
125 writeln
!(f
, "\t{:>3} players online\n", players
.len())?
;
126 if players
.len() > 0 {
127 writeln
!(f
, "{:^5} {:^30} {:^4} {:^10} {}", "Index", "Player name", "Score", "Duration", "The Ship")?
;
128 for player
in players
{
129 writeln
!(f
, "{:>5} {:<30} {:>4} {:>10} {}",
134 TheShipPlayer(&player
.the_ship
))?
;
140 writeln
!(f
, "\nFailed to query players:\t{}", e
)
145 #[derive(Debug, Serialize)]
148 result
: Result
<Vec
<HLQueryResult
>, HLQueryError
>
152 fn new
<S
: Into
<String
>>(input
: S
, result
: Result
<Vec
<HLQueryResult
>, HLQueryError
>) -> Self {
153 let input
= input
.into
();
154 Self { input
, result
}
158 impl Display
for HLQuery
{
159 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
160 writeln
!(f
, "{}", "*".repeat(SEP_WIDTH
))?
;
161 write_datapoint
!(f
, PAD_WIDTH
, "Input:", self.inp
ut
)?
;
164 write_datapoint
!(f
, PAD_WIDTH
, "Number of addresses:", format
!("{}", v
.len()))?
;
165 writeln
!(f
, "{}\n", "*".repeat(SEP_WIDTH
))?
;
172 writeln
!(f
, "Failed to execute query:\t{e}\n")
179 IOError(std
::io
::Error
),
180 A2SError(a2s
::errors
::Error
)
183 impl Display
for HLQueryError
{
184 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
186 IOError(e
) => write
!(f
, "{:?}", e
),
187 A2SError(e
) => write
!(f
, "{:?}", e
)
192 impl Error
for HLQueryError
{}
194 impl Serialize
for HLQueryError
{
195 fn serialize
<S
>(&self, serializer
: S
) -> Result
<S
::Ok
, S
::Error
>
199 serializer
.serialize_str(&format
!("{self}"))
203 impl From
<std
::io
::Error
> for HLQueryError
{
204 fn from(e
: std
::io
::Error
) -> Self {
209 impl From
<a2s
::errors
::Error
> for HLQueryError
{
210 fn from(e
: a2s
::errors
::Error
) -> Self {
217 let cli
= Cli
::parse();
219 let client
= A2SClient
::new().unwrap
();
220 let query_results
: Vec
<HLQuery
> = cli
.addresses
.iter
()
222 let addresses
= arg
.to_socket_addrs();
225 .map(|lookup_result
| match lookup_result
{
226 (input
, Ok(iter_addr
)) => {
227 (input
, Ok(iter_addr
.filter
_map
(|sa
| match sa
{
228 SocketAddr
::V4(sa4
) => Some(sa4
),
232 (input
, Err(e
)) => (input
, Err(HLQueryError
::IOError(e
)))
234 .map(|(input
, address_group
)| HLQuery
::new(input
, address_group
.map(
235 |addresses
| addresses
.map(|addr
| HLQueryResult
::new(&client
, addr
)).collect())))
240 println
!("{}", serde_json
::to_string_pretty(&query_results
).unwrap
());
243 println
!("{}", serde_json
::to_string(&query_results
).unwrap
());
248 println
!("{:#?}", query_results
);
251 println
!("{:?}", query_results
);
255 for query
in query_results
{
256 println
!("{}", query
);