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}