msq/
filter.rs

1//! Filter builder - Construct your filter to filter out server results
2//!
3//! **NOTE**: Some filters may or may not work as expected depending on
4//! appid/games you try it on. The filter builder methods and string
5//! construction generally follows close to the reference listed out
6//! in the Valve developer wiki.
7//!
8//! Reference: <https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol#Filter>
9//!
10//! # Quick Start
11//!
12//! ```
13//! use msq::Filter;
14//! let filter = Filter::new()
15//!     .appid(240)
16//!     .full(false)
17//!     .map("de_dust2");
18//! ```
19//!
20#[derive(Clone)]
21enum FilterPropVal {
22    Special(Vec<FilterProp>),
23    Boolean(bool),
24    Str(String),
25    Uint32(u32),
26    Tags(Vec<String>),
27}
28
29impl FilterPropVal {
30    fn from_special(spec: &Vec<FilterProp>) -> FilterPropVal {
31        Self::Special(spec.clone())
32    }
33
34    fn from_tags(tags: &Vec<&str>) -> FilterPropVal {
35        let mut fpvtags: Vec<String> = vec![];
36
37        for tag in tags {
38            fpvtags.push(String::from(*tag));
39        }
40
41        Self::Tags(fpvtags)
42    }
43
44    fn as_str(&self) -> String {
45        match &*self {
46            Self::Special(filterprops) => {
47                let mut sstr = String::from("");
48
49                // Start with values count
50                sstr += &format!("{}", filterprops.len());
51
52                // Populate the string with inner values
53                for fp in filterprops {
54                    sstr += &fp.as_str();
55                }
56
57                sstr
58            }
59            Self::Boolean(b) => format!("{}", *b as i32),
60            Self::Str(s) => String::from(s),
61            Self::Uint32(i) => format!("{}", i),
62            Self::Tags(tags) => {
63                let mut tags_str = String::from("");
64                for tag in tags {
65                    tags_str += &tag;
66                    tags_str += ",";
67                }
68                tags_str.pop();
69                tags_str
70            }
71        }
72    }
73}
74
75#[derive(Clone)]
76struct FilterProp {
77    pub name: String,
78    pub value: FilterPropVal,
79}
80
81impl FilterProp {
82    fn new(name: &str, value: FilterPropVal) -> FilterProp {
83        FilterProp {
84            name: String::from(name),
85            value: value,
86        }
87    }
88
89    fn as_str(&self) -> String {
90        format!("\\{}\\{}", self.name, self.value.as_str())
91    }
92}
93
94/// Filter builder - Construct your filter to filter out server results
95///
96/// * Intended to be used with: [`MSQClient`](crate::MSQClient) and
97/// [`MSQClientBlock`](crate::MSQClientBlock)
98/// * **NOTE**: Some filters may or may not work as expected depending on
99/// appid/games you try it on. The filter builder methods and string
100/// construction generally follows close to the reference listed out
101/// in the Valve developer wiki.
102/// * Reference: <https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol#Filter>
103///
104/// # Quick Start
105/// ```rust
106/// use msq::Filter;
107///
108/// let filter = Filter::new()      // Create a Filter builder
109///         .appid(240)             // appid of 240 (CS:S)
110///         .nand()                 // Start of NAND special filter
111///             .map("de_dust2")        // Map is de_dust2
112///             .empty(true)            // Server is empty
113///         .end()                  // End of NAND special filter
114///         .gametype(&vec!["friendlyfire", "alltalk"]);
115/// ```
116///
117pub struct Filter {
118    filter_lst: Vec<FilterProp>,
119    in_special: bool,
120    spec_vec: Vec<FilterProp>,
121    special_name: String,
122}
123
124impl Filter {
125    /// Returns a string representing the filters
126    #[deprecated(since = "0.2.0", note = "Replaced with as_string (name change)")]
127    pub fn as_str(&self) -> String {
128        self.as_string()
129    }
130
131    /// Returns a string representing the filters
132    pub fn as_string(&self) -> String {
133        let mut sstr = String::from("");
134
135        for fp in &self.filter_lst {
136            sstr += &fp.as_str();
137        }
138
139        sstr
140    }
141
142    /// Returns a new Filter struct, used for string builder
143    ///
144    /// # Examples
145    /// ```
146    /// // Filter
147    /// use msq::Filter;
148    /// let filter = Filter::new()
149    ///     .appid(240)
150    ///     .full(false)
151    ///     .map("de_dust2");
152    /// ```
153    pub fn new() -> Filter {
154        Filter {
155            filter_lst: vec![],
156            in_special: false,
157            spec_vec: vec![],
158            special_name: String::from(""),
159        }
160    }
161
162    fn push(mut self, name: &str, value: FilterPropVal) -> Filter {
163        if self.in_special {
164            self.spec_vec.push(FilterProp::new(name, value));
165        } else {
166            self.filter_lst.push(FilterProp::new(name, value));
167        }
168        self
169    }
170
171    // Generic filter: Boolean
172    fn boolean(self, name: &str, switch: bool) -> Filter {
173        self.push(name, FilterPropVal::Boolean(switch))
174    }
175
176    // Generic filter: String
177    fn string(self, name: &str, param: &str) -> Filter {
178        self.push(name, FilterPropVal::Str(String::from(param)))
179    }
180
181    // Generic filter: Unsigned integer of 32 bits
182    fn uint32(self, name: &str, num: u32) -> Filter {
183        self.push(name, FilterPropVal::Uint32(num))
184    }
185
186    // Generic filter: Vector of strings
187    fn vecstr(self, name: &str, tags: &Vec<&str>) -> Filter {
188        if tags.len() > 0 {
189            self.push(name, FilterPropVal::from_tags(tags))
190        } else {
191            self
192        }
193    }
194
195    // Generic filter: Special (start)
196    fn special_start(mut self, name: &str) -> Filter {
197        self.spec_vec.clear();
198        self.in_special = true;
199        self.special_name = String::from(name);
200        self
201    }
202
203    /// A special filter, specifies that servers matching any of the following \[x\] conditions should not be returned.
204    /// See [`end`](#method.end) method to see examples on usage.
205    pub fn nor(self) -> Filter {
206        self.special_start("nor")
207    }
208
209    /// A special filter, specifies that servers matching all of the following \[x\] conditions should not be returned.
210    /// See [`end`](#method.end) method to see examples on usage.
211    pub fn nand(self) -> Filter {
212        self.special_start("nand")
213    }
214
215    /// End the special filter (nor, nand)
216    /// You must use this method after each nor/nand special filter method being used
217    ///
218    /// # Examples
219    /// Using the NAND filter:
220    /// ```
221    /// use msq::Filter;
222    /// let filter = Filter::new()
223    ///     .appid(240)
224    ///     .nand()     // Exclude servers that has de_dust2 AND is empty
225    ///         .map("de_dust2")
226    ///         .empty(true)
227    ///     .end()      // Ends the NAND special filter
228    ///     .gametype(&vec!["friendlyfire", "alltalk"]);
229    /// ```
230    ///
231    /// Using the NOR filter:
232    /// ```
233    /// use msq::Filter;
234    /// let filter = Filter::new()
235    ///     .appid(240)
236    ///     .nor()      // Exclude servers that has de_dust2 OR is empty
237    ///         .map("de_dust2")
238    ///         .empty(true)
239    ///     .end()      // Ends the NOR special filter
240    ///     .gametype(&vec!["friendlyfire", "alltalk"]);
241    /// ```
242    pub fn end(mut self) -> Filter {
243        self.filter_lst.push(FilterProp::new(
244            &self.special_name,
245            FilterPropVal::from_special(&self.spec_vec),
246        ));
247        self.in_special = false;
248        self.special_name = String::from("");
249        self
250    }
251
252    /// Filters if the servers running dedicated
253    ///
254    /// # Arguments
255    /// * `is_dedicated` - `true` = dedicated, `false` = not dedicated
256    pub fn dedicated(self, is_dedicated: bool) -> Filter {
257        self.boolean("dedicated", is_dedicated)
258    }
259
260    /// Servers using anti-cheat technology (VAC, but potentially others as well)
261    ///
262    /// # Arguments
263    /// * `hasac` - `true` = secure, `false` = not secure
264    pub fn secure(self, hasac: bool) -> Filter {
265        self.boolean("secure", hasac)
266    }
267
268    /// Servers running the specified modification (ex: cstrike)
269    ///
270    /// # Arguments
271    /// * `modg` - The modification name (ex: `cstrike`)
272    pub fn gamedir(self, modg: &str) -> Filter {
273        self.string("gamedir", modg)
274    }
275
276    /// Servers running the specified map (ex: cs_italy)
277    ///
278    /// # Arguments
279    /// * `mapn` - The current map it's playing (ex: `cs_italy`)
280    pub fn map(self, mapn: &str) -> Filter {
281        self.string("map", mapn)
282    }
283
284    /// Servers running on a Linux platform
285    ///
286    /// # Arguments
287    /// * `runslinux` - `true` = Runs on Linux, `false` = Does not runs on Linux
288    pub fn linux(self, runslinux: bool) -> Filter {
289        self.boolean("linux", runslinux)
290    }
291
292    /// Servers that are password protected
293    ///
294    /// # Arguments
295    /// * `protected` - `true` = Password protected, `false` = Not password protected
296    pub fn password(self, protected: bool) -> Filter {
297        self.boolean("password", protected)
298    }
299
300    /// Servers that are full
301    ///
302    /// # Arguments
303    /// * `is_full` - `true` = Server's full, `false` = Server's not full
304    pub fn full(self, is_full: bool) -> Filter {
305        self.boolean("full", !is_full)
306    }
307
308    /// Servers that are spectator proxies
309    ///
310    /// # Arguments
311    /// * `specprox` - `true` = A spectator proxies, `false` = Not a spectator proxies
312    pub fn proxy(self, specprox: bool) -> Filter {
313        self.boolean("proxy", specprox)
314    }
315
316    /// Servers that are running game \[appid\]
317    ///
318    /// # Arguments
319    /// * `appid` - The appid of the server: (EX: `240` (for CS:S))
320    pub fn appid(self, appid: u32) -> Filter {
321        self.uint32("appid", appid)
322    }
323
324    /// Servers that are NOT running game \[appid\]
325    ///
326    /// # Arguments
327    /// * `appid` - The appid of the server: (EX: `240` (for CS:S))
328    pub fn napp(self, appid: u32) -> Filter {
329        self.uint32("napp", appid)
330    }
331
332    /// Servers that are empty
333    ///
334    /// # Arguments
335    /// * `is_empty` - `true` = Empty, `false` = Not empty
336    pub fn empty(self, is_empty: bool) -> Filter {
337        if is_empty {
338            self.boolean("noplayers", true)
339        } else {
340            self.boolean("empty", true)
341        }
342    }
343
344    /// Servers that are whitelisted
345    ///
346    /// # Arguments
347    /// * `white` - `true` = Whitelisted, `false` = Not whitelisted
348    pub fn whitelisted(self, white: bool) -> Filter {
349        self.boolean("white", white)
350    }
351
352    /// Servers with all of the given tag(s) in sv_tags
353    ///
354    /// # Arguments
355    /// * `tags` - A vector of strings which represents a tag from sv_tags
356    ///
357    /// # Example
358    /// ```
359    /// use msq::Filter;
360    /// let filter = Filter::new()
361    ///     .appid(240)
362    ///     .gametype(&vec!["friendlyfire", "alltalk"]);
363    /// ```
364    ///
365    /// If you put in an empty vector, it will return nothing
366    pub fn gametype(self, tags: &Vec<&str>) -> Filter {
367        self.vecstr("gametype", tags)
368    }
369
370    /// Servers with all of the given tag(s) in their 'hidden' tags (L4D2)
371    ///
372    /// # Arguments
373    /// * `tags` - A vector of strings which represents a tag from sv_tags
374    pub fn gamedata(self, tags: &Vec<&str>) -> Filter {
375        self.vecstr("gamedata", tags)
376    }
377
378    /// Servers with any of the given tag(s) in their 'hidden' tags (L4D2)
379    ///
380    /// # Arguments
381    /// * `tags` - A vector of strings which represents a tag from sv_tags
382    pub fn gamedataor(self, tags: &Vec<&str>) -> Filter {
383        self.vecstr("gamedataor", tags)
384    }
385
386    /// Servers with their hostname matching \[hostname\] (can use * as a wildcard)
387    ///
388    /// # Arguments
389    /// * `hostname` - String of matching hostname (EX: `1.2.*`)
390    pub fn name_match(self, hostname: &str) -> Filter {
391        self.string("name_match", hostname)
392    }
393
394    /// Servers running version \[version\] (can use * as a wildcard)
395    ///
396    /// # Arguments
397    /// * `ver` - String of matching version
398    pub fn version_match(self, ver: &str) -> Filter {
399        self.string("version_match", ver)
400    }
401
402    /// Return only one server for each unique IP address matched
403    ///
404    /// # Arguments
405    /// * `one_server` - `true` = Return one server
406    pub fn collapse_addr_hash(self, one_server: bool) -> Filter {
407        self.boolean("collapse_addr_hash", one_server)
408    }
409
410    /// Return only servers on the specified IP address (port supported and optional)
411    ///
412    /// # Arguments
413    /// * `ipaddr` - String of the IP address to match
414    pub fn gameaddr(self, ipaddr: &str) -> Filter {
415        self.string("gameaddr", ipaddr)
416    }
417}