mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-06-01 09:42:41 +00:00
[Generic] Add struct for all extra request settings (#93)
* [Generic] Add struct for all extra request settings Adds a new struct `ExtraRequestSettings` that contains all possible extra settings for all protocols, and can be implicitly converted with `.into()` into each protocol's extra settings type. This is then used in a new query method `query_with_timeout_and_extra_settings()` that passes the object on to the selected protocol. This also updates the generic example to set some of these generic settings so that it can be used for certain queries like "mc.hypixel.net". * [Minecraft] Add `request_settings` parameter to auto query This allows generic queries to pass through request settings when using the `mc` game so that servers like `mc.hypixel.net` will still work when using auto query. * Fix generic examples tests (and enable example tests in pre-commit)
This commit is contained in:
parent
76a3ac2f78
commit
6c1fdb1159
9 changed files with 175 additions and 20 deletions
|
|
@ -26,7 +26,7 @@ repos:
|
||||||
language: system
|
language: system
|
||||||
files: '[.]rs$'
|
files: '[.]rs$'
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
entry: cargo test
|
entry: cargo test --bins --lib --examples --all-features
|
||||||
- id: format
|
- id: format
|
||||||
name: Check rustfmt
|
name: Check rustfmt
|
||||||
language: system
|
language: system
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ Crate:
|
||||||
- Rich errors, capturing backtrace is done on `RUST_BACKTRACE=1`.
|
- Rich errors, capturing backtrace is done on `RUST_BACKTRACE=1`.
|
||||||
- Applied some nursery Clippy lints.
|
- Applied some nursery Clippy lints.
|
||||||
|
|
||||||
|
Generics:
|
||||||
|
- Added `ExtraRequestSettings` containing all possible extra request settings.
|
||||||
|
- Added `query_with_timeout_and_extra_settings()` to allow generic queries with extra settings.
|
||||||
|
|
||||||
### Breaking...
|
### Breaking...
|
||||||
Crate:
|
Crate:
|
||||||
- The enum used for errors, `GDError` has been renamed to `GDErrorKind`.
|
- The enum used for errors, `GDError` has been renamed to `GDErrorKind`.
|
||||||
|
|
@ -56,6 +60,9 @@ Protocols:
|
||||||
- Minecraft Bedrock
|
- Minecraft Bedrock
|
||||||
1. Renamed `version_protocol` to `protocol_version`.
|
1. Renamed `version_protocol` to `protocol_version`.
|
||||||
|
|
||||||
|
- Minecraft:
|
||||||
|
1. Added `request_settings` parameter to `query()`
|
||||||
|
|
||||||
- The Ship:
|
- The Ship:
|
||||||
1. Renamed `protocol` to `protocol_version`.
|
1. Renamed `protocol` to `protocol_version`.
|
||||||
2. Renamed `max_players` to `players_maximum` and changed its type from `u64` to `u32`.
|
2. Renamed `max_players` to `players_maximum` and changed its type from `u64` to `u32`.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use gamedig::{
|
use gamedig::{
|
||||||
protocols::types::{CommonResponse, TimeoutSettings},
|
protocols::types::{CommonResponse, ExtraRequestSettings, TimeoutSettings},
|
||||||
query_with_timeout,
|
query_with_timeout_and_extra_settings,
|
||||||
GDResult,
|
GDResult,
|
||||||
GAMES,
|
GAMES,
|
||||||
};
|
};
|
||||||
|
|
@ -13,6 +13,7 @@ fn generic_query(
|
||||||
addr: &IpAddr,
|
addr: &IpAddr,
|
||||||
port: Option<u16>,
|
port: Option<u16>,
|
||||||
timeout_settings: Option<TimeoutSettings>,
|
timeout_settings: Option<TimeoutSettings>,
|
||||||
|
extra_settings: Option<ExtraRequestSettings>,
|
||||||
) -> GDResult<Box<dyn CommonResponse>> {
|
) -> GDResult<Box<dyn CommonResponse>> {
|
||||||
let game = GAMES
|
let game = GAMES
|
||||||
.get(game_name)
|
.get(game_name)
|
||||||
|
|
@ -20,7 +21,7 @@ fn generic_query(
|
||||||
|
|
||||||
println!("Querying {:#?} with game {:#?}.", addr, game);
|
println!("Querying {:#?} with game {:#?}.", addr, game);
|
||||||
|
|
||||||
let response = query_with_timeout(game, addr, port, timeout_settings)?;
|
let response = query_with_timeout_and_extra_settings(game, addr, port, timeout_settings, extra_settings)?;
|
||||||
println!("Response: {:#?}", response.as_json());
|
println!("Response: {:#?}", response.as_json());
|
||||||
|
|
||||||
let common = response.as_original();
|
let common = response.as_original();
|
||||||
|
|
@ -34,16 +35,22 @@ fn main() {
|
||||||
|
|
||||||
// Handle arguments
|
// Handle arguments
|
||||||
if let Some(game_name) = args.next() {
|
if let Some(game_name) = args.next() {
|
||||||
|
let hostname = args.next().expect("Must provide an address");
|
||||||
// Use to_socket_addrs to resolve hostname to IP
|
// Use to_socket_addrs to resolve hostname to IP
|
||||||
let addr: SocketAddr = args
|
let addr: SocketAddr = format!("{}:0", hostname)
|
||||||
.next()
|
.to_socket_addrs()
|
||||||
.map(|s| format!("{}:0", s).to_socket_addrs().unwrap())
|
.unwrap()
|
||||||
.expect("Must provide address")
|
|
||||||
.next()
|
.next()
|
||||||
.expect("Could not lookup host");
|
.expect("Could not lookup host");
|
||||||
let port: Option<u16> = args.next().map(|s| s.parse().unwrap());
|
let port: Option<u16> = args.next().map(|s| s.parse().unwrap());
|
||||||
|
|
||||||
generic_query(&game_name, &addr.ip(), port, None).unwrap();
|
let extra_settings = ExtraRequestSettings::default()
|
||||||
|
.set_hostname(hostname.to_string())
|
||||||
|
.set_gather_rules(true)
|
||||||
|
.set_gather_players(true)
|
||||||
|
.set_check_app_id(false);
|
||||||
|
|
||||||
|
generic_query(&game_name, &addr.ip(), port, None, Some(extra_settings)).unwrap();
|
||||||
} else {
|
} else {
|
||||||
// Without arguments print a list of games
|
// Without arguments print a list of games
|
||||||
|
|
||||||
|
|
@ -68,7 +75,7 @@ mod test {
|
||||||
fn test_game(game_name: &str) {
|
fn test_game(game_name: &str) {
|
||||||
let timeout_settings =
|
let timeout_settings =
|
||||||
Some(TimeoutSettings::new(Some(Duration::from_nanos(1)), Some(Duration::from_nanos(1))).unwrap());
|
Some(TimeoutSettings::new(Some(Duration::from_nanos(1)), Some(Duration::from_nanos(1))).unwrap());
|
||||||
assert!(generic_query(game_name, &ADDR, None, timeout_settings).is_err());
|
assert!(generic_query(game_name, &ADDR, None, timeout_settings, None).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ pub mod warsow;
|
||||||
|
|
||||||
use crate::protocols::gamespy::GameSpyVersion;
|
use crate::protocols::gamespy::GameSpyVersion;
|
||||||
use crate::protocols::quake::QuakeVersion;
|
use crate::protocols::quake::QuakeVersion;
|
||||||
use crate::protocols::types::{CommonResponse, ProprietaryProtocol, TimeoutSettings};
|
use crate::protocols::types::{CommonResponse, ExtraRequestSettings, ProprietaryProtocol, TimeoutSettings};
|
||||||
use crate::protocols::{self, Protocol};
|
use crate::protocols::{self, Protocol};
|
||||||
use crate::GDResult;
|
use crate::GDResult;
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
|
|
@ -142,26 +142,50 @@ mod definitions;
|
||||||
pub use definitions::GAMES;
|
pub use definitions::GAMES;
|
||||||
|
|
||||||
/// Make a query given a game definition
|
/// Make a query given a game definition
|
||||||
|
#[inline]
|
||||||
pub fn query(game: &Game, address: &IpAddr, port: Option<u16>) -> GDResult<Box<dyn CommonResponse>> {
|
pub fn query(game: &Game, address: &IpAddr, port: Option<u16>) -> GDResult<Box<dyn CommonResponse>> {
|
||||||
query_with_timeout(game, address, port, None)
|
query_with_timeout_and_extra_settings(game, address, port, None, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make a query given a game definition and timeout settings
|
/// Make a query given a game definition and timeout settings
|
||||||
|
#[inline]
|
||||||
pub fn query_with_timeout(
|
pub fn query_with_timeout(
|
||||||
game: &Game,
|
game: &Game,
|
||||||
address: &IpAddr,
|
address: &IpAddr,
|
||||||
port: Option<u16>,
|
port: Option<u16>,
|
||||||
timeout_settings: Option<TimeoutSettings>,
|
timeout_settings: Option<TimeoutSettings>,
|
||||||
|
) -> GDResult<Box<dyn CommonResponse>> {
|
||||||
|
query_with_timeout_and_extra_settings(game, address, port, timeout_settings, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make a query given a game definition, timeout settings, and extra settings
|
||||||
|
pub fn query_with_timeout_and_extra_settings(
|
||||||
|
game: &Game,
|
||||||
|
address: &IpAddr,
|
||||||
|
port: Option<u16>,
|
||||||
|
timeout_settings: Option<TimeoutSettings>,
|
||||||
|
extra_settings: Option<ExtraRequestSettings>,
|
||||||
) -> GDResult<Box<dyn CommonResponse>> {
|
) -> GDResult<Box<dyn CommonResponse>> {
|
||||||
let socket_addr = SocketAddr::new(*address, port.unwrap_or(game.default_port));
|
let socket_addr = SocketAddr::new(*address, port.unwrap_or(game.default_port));
|
||||||
Ok(match &game.protocol {
|
Ok(match &game.protocol {
|
||||||
Protocol::Valve(steam_app) => {
|
Protocol::Valve(steam_app) => {
|
||||||
protocols::valve::query(&socket_addr, steam_app.as_engine(), None, timeout_settings).map(Box::new)?
|
protocols::valve::query(
|
||||||
|
&socket_addr,
|
||||||
|
steam_app.as_engine(),
|
||||||
|
extra_settings.map(ExtraRequestSettings::into),
|
||||||
|
timeout_settings,
|
||||||
|
)
|
||||||
|
.map(Box::new)?
|
||||||
}
|
}
|
||||||
Protocol::Minecraft(version) => {
|
Protocol::Minecraft(version) => {
|
||||||
match version {
|
match version {
|
||||||
Some(protocols::minecraft::Server::Java) => {
|
Some(protocols::minecraft::Server::Java) => {
|
||||||
protocols::minecraft::query_java(&socket_addr, timeout_settings, None).map(Box::new)?
|
protocols::minecraft::query_java(
|
||||||
|
&socket_addr,
|
||||||
|
timeout_settings,
|
||||||
|
extra_settings.map(ExtraRequestSettings::into),
|
||||||
|
)
|
||||||
|
.map(Box::new)?
|
||||||
}
|
}
|
||||||
Some(protocols::minecraft::Server::Bedrock) => {
|
Some(protocols::minecraft::Server::Bedrock) => {
|
||||||
protocols::minecraft::query_bedrock(&socket_addr, timeout_settings).map(Box::new)?
|
protocols::minecraft::query_bedrock(&socket_addr, timeout_settings).map(Box::new)?
|
||||||
|
|
@ -169,7 +193,14 @@ pub fn query_with_timeout(
|
||||||
Some(protocols::minecraft::Server::Legacy(group)) => {
|
Some(protocols::minecraft::Server::Legacy(group)) => {
|
||||||
protocols::minecraft::query_legacy_specific(*group, &socket_addr, timeout_settings).map(Box::new)?
|
protocols::minecraft::query_legacy_specific(*group, &socket_addr, timeout_settings).map(Box::new)?
|
||||||
}
|
}
|
||||||
None => protocols::minecraft::query(&socket_addr, timeout_settings).map(Box::new)?,
|
None => {
|
||||||
|
protocols::minecraft::query(
|
||||||
|
&socket_addr,
|
||||||
|
timeout_settings,
|
||||||
|
extra_settings.map(ExtraRequestSettings::into),
|
||||||
|
)
|
||||||
|
.map(Box::new)?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Protocol::Gamespy(version) => {
|
Protocol::Gamespy(version) => {
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,12 @@ mod legacy_v1_6;
|
||||||
|
|
||||||
/// Queries a Minecraft server with all the protocol variants one by one (Java
|
/// Queries a Minecraft server with all the protocol variants one by one (Java
|
||||||
/// -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)).
|
/// -> Bedrock -> Legacy (1.6 -> 1.4 -> Beta 1.8)).
|
||||||
pub fn query(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<JavaResponse> {
|
pub fn query(
|
||||||
if let Ok(response) = query_java(address, timeout_settings.clone(), None) {
|
address: &SocketAddr,
|
||||||
|
timeout_settings: Option<TimeoutSettings>,
|
||||||
|
request_settings: Option<RequestSettings>,
|
||||||
|
) -> GDResult<JavaResponse> {
|
||||||
|
if let Ok(response) = query_java(address, timeout_settings.clone(), request_settings) {
|
||||||
return Ok(response);
|
return Ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
protocols::{
|
protocols::{
|
||||||
types::{CommonPlayer, CommonResponse, GenericPlayer},
|
types::{CommonPlayer, CommonResponse, ExtraRequestSettings, GenericPlayer},
|
||||||
GenericResponse,
|
GenericResponse,
|
||||||
},
|
},
|
||||||
GDErrorKind::{InvalidInput, PacketBad, UnknownEnumCast},
|
GDErrorKind::{InvalidInput, PacketBad, UnknownEnumCast},
|
||||||
|
|
@ -110,6 +110,16 @@ impl Default for RequestSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ExtraRequestSettings> for RequestSettings {
|
||||||
|
fn from(value: ExtraRequestSettings) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
Self {
|
||||||
|
hostname: value.hostname.unwrap_or(default.hostname),
|
||||||
|
protocol_version: value.protocol_version.unwrap_or(default.protocol_version),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CommonResponse for JavaResponse {
|
impl CommonResponse for JavaResponse {
|
||||||
fn as_original(&self) -> GenericResponse { GenericResponse::Minecraft(VersionedResponse::Java(self)) }
|
fn as_original(&self) -> GenericResponse { GenericResponse::Minecraft(VersionedResponse::Java(self)) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,4 @@ pub mod types;
|
||||||
/// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries)
|
/// Reference: [Server Query](https://developer.valvesoftware.com/wiki/Server_queries)
|
||||||
pub mod valve;
|
pub mod valve;
|
||||||
|
|
||||||
pub use types::{GenericResponse, Protocol};
|
pub use types::{ExtraRequestSettings, GenericResponse, Protocol};
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,82 @@ impl Default for TimeoutSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generic extra request settings
|
||||||
|
///
|
||||||
|
/// Fields of this struct may not be used depending on which protocol is
|
||||||
|
/// selected, the individual fields link to the specific places they will be
|
||||||
|
/// used with additional documentation.
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
/// Create minecraft settings with builder:
|
||||||
|
/// ```
|
||||||
|
/// use gamedig::protocols::{minecraft, ExtraRequestSettings};
|
||||||
|
/// let mc_settings: minecraft::RequestSettings = ExtraRequestSettings::default().set_hostname("mc.hypixel.net".to_string()).into();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Create valve settings with builder:
|
||||||
|
/// ```
|
||||||
|
/// use gamedig::protocols::{valve, ExtraRequestSettings};
|
||||||
|
/// let valve_settings: valve::GatheringSettings = ExtraRequestSettings::default().set_check_app_id(false).into();
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct ExtraRequestSettings {
|
||||||
|
/// The server's hostname.
|
||||||
|
///
|
||||||
|
/// Used by:
|
||||||
|
/// - [minecraft::RequestSettings#structfield.hostname]
|
||||||
|
pub hostname: Option<String>,
|
||||||
|
/// The protocol version to use.
|
||||||
|
///
|
||||||
|
/// Used by:
|
||||||
|
/// - [minecraft::RequestSettings#structfield.protocol_version]
|
||||||
|
pub protocol_version: Option<i32>,
|
||||||
|
/// Whether to gather player information
|
||||||
|
///
|
||||||
|
/// Used by:
|
||||||
|
/// - [valve::GatheringSettings#structfield.players]
|
||||||
|
pub gather_players: Option<bool>,
|
||||||
|
/// Whether to gather rule information.
|
||||||
|
///
|
||||||
|
/// Used by:
|
||||||
|
/// - [valve::GatheringSettings#structfield.rules]
|
||||||
|
pub gather_rules: Option<bool>,
|
||||||
|
/// Whether to check if the App ID is valid.
|
||||||
|
///
|
||||||
|
/// Used by:
|
||||||
|
/// - [valve::GatheringSettings#structfield.check_app_id]
|
||||||
|
pub check_app_id: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtraRequestSettings {
|
||||||
|
/// [Sets hostname](ExtraRequestSettings#structfield.hostname)
|
||||||
|
pub fn set_hostname(mut self, hostname: String) -> Self {
|
||||||
|
self.hostname = Some(hostname);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// [Sets protocol
|
||||||
|
/// version](ExtraRequestSettings#structfield.protocol_version)
|
||||||
|
pub fn set_protocol_version(mut self, protocol_version: i32) -> Self {
|
||||||
|
self.protocol_version = Some(protocol_version);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// [Sets gather players](ExtraRequestSettings#structfield.gather_players)
|
||||||
|
pub fn set_gather_players(mut self, gather_players: bool) -> Self {
|
||||||
|
self.gather_players = Some(gather_players);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// [Sets gather rules](ExtraRequestSettings#structfield.gather_rules)
|
||||||
|
pub fn set_gather_rules(mut self, gather_rules: bool) -> Self {
|
||||||
|
self.gather_rules = Some(gather_rules);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// [Sets check app ID](ExtraRequestSettings#structfield.check_app_id)
|
||||||
|
pub fn set_check_app_id(mut self, check_app_id: bool) -> Self {
|
||||||
|
self.check_app_id = Some(check_app_id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -235,4 +311,13 @@ mod tests {
|
||||||
assert_eq!(default_settings.get_read(), Some(Duration::from_secs(4)));
|
assert_eq!(default_settings.get_read(), Some(Duration::from_secs(4)));
|
||||||
assert_eq!(default_settings.get_write(), Some(Duration::from_secs(4)));
|
assert_eq!(default_settings.get_write(), Some(Duration::from_secs(4)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that extra request settings can be converted
|
||||||
|
#[test]
|
||||||
|
fn test_extra_request_settings() {
|
||||||
|
let settings = ExtraRequestSettings::default();
|
||||||
|
|
||||||
|
let _: minecraft::RequestSettings = settings.clone().into();
|
||||||
|
let _: valve::GatheringSettings = settings.into();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer};
|
use crate::protocols::types::{CommonPlayer, CommonResponse, ExtraRequestSettings, GenericPlayer};
|
||||||
use crate::GDErrorKind::UnknownEnumCast;
|
use crate::GDErrorKind::UnknownEnumCast;
|
||||||
use crate::GDResult;
|
use crate::GDResult;
|
||||||
use crate::{buffer::Buffer, protocols::GenericResponse};
|
use crate::{buffer::Buffer, protocols::GenericResponse};
|
||||||
|
|
@ -430,6 +430,17 @@ impl Default for GatheringSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ExtraRequestSettings> for GatheringSettings {
|
||||||
|
fn from(value: ExtraRequestSettings) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
Self {
|
||||||
|
players: value.gather_players.unwrap_or(default.players),
|
||||||
|
rules: value.gather_rules.unwrap_or(default.rules),
|
||||||
|
check_app_id: value.check_app_id.unwrap_or(default.check_app_id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Generic response types that are used by many games, they are the protocol
|
/// Generic response types that are used by many games, they are the protocol
|
||||||
/// ones, but without the unnecessary bits (example: the **The Ship**-only
|
/// ones, but without the unnecessary bits (example: the **The Ship**-only
|
||||||
/// fields).
|
/// fields).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue