hydrus_api/wrapper/builders/
tag_builder.rs

1use crate::utils::{format_datetime, format_duration};
2use crate::wrapper::service::ServiceName;
3use crate::wrapper::tag::Tag;
4use chrono::{Datelike, Duration};
5use mime::Mime;
6use std::fmt::{Display, Formatter};
7
8#[derive(Clone, Debug)]
9pub struct TagBuilder {
10    negated: bool,
11    name: String,
12    namespace: Option<String>,
13}
14
15impl TagBuilder {
16    pub fn new<S: ToString>(name: S) -> Self {
17        Self {
18            negated: false,
19            name: name.to_string(),
20            namespace: None,
21        }
22    }
23
24    /// Set a namespace for the tag
25    pub fn namespace<S: ToString>(mut self, namespace: S) -> Self {
26        self.namespace = Some(namespace.to_string());
27
28        self
29    }
30
31    /// Converts the builder into a system tag builder
32    pub fn system(self) -> SystemTagBuilder {
33        SystemTagBuilder {
34            negated: false,
35            name: self.name,
36        }
37    }
38
39    /// Negates the tag.
40    /// if it has already been negated it will be positive again
41    pub fn negate(mut self) -> Self {
42        self.negated = !self.negated;
43
44        self
45    }
46
47    pub fn build(self) -> Tag {
48        Tag {
49            negated: self.negated,
50            name: self.name,
51            namespace: self.namespace,
52        }
53    }
54}
55
56#[derive(Clone, Debug)]
57pub struct SystemTagBuilder {
58    name: String,
59    negated: bool,
60}
61
62impl SystemTagBuilder {
63    pub fn new() -> SystemTagBuilder {
64        SystemTagBuilder {
65            name: String::new(),
66            negated: false,
67        }
68    }
69
70    pub fn build(self) -> Tag {
71        Tag {
72            negated: self.negated,
73            name: self.name,
74            namespace: Some(String::from("system")),
75        }
76    }
77
78    /// Negates the tag.
79    /// if it has already been negated it will be positive again
80    pub fn negate(mut self) -> Self {
81        self.negated = !self.negated;
82
83        self
84    }
85
86    /// All files stored in the client
87    pub fn everything(self) -> Self {
88        self.change_name("everything")
89    }
90
91    /// Files stored in the inbox
92    pub fn inbox(self) -> Self {
93        self.change_name("inbox")
94    }
95
96    /// Archived files
97    pub fn archive(self) -> Self {
98        self.change_name("archive")
99    }
100
101    /// Files that have a duration (e.g. videos)
102    pub fn has_duration(self) -> Self {
103        self.change_name("has duration")
104    }
105
106    /// Files that don't have a duration
107    pub fn no_duration(self) -> Self {
108        self.change_name("no duration")
109    }
110
111    /// Files with a specific duration
112    pub fn duration(self, comparator: Comparator, value: u64, unit: DurationUnit) -> Self {
113        self.change_name(format!("duration {} {} {}", comparator, value, unit))
114    }
115
116    /// Files that have the best quality in their duplicate group
117    pub fn best_duplicate_quality(self) -> Self {
118        self.change_name("best quality of group")
119    }
120
121    /// Files that don't have the best quality in their duplicate group
122    pub fn not_best_duplicate_quality(self) -> Self {
123        self.change_name("isn't best quality of group")
124    }
125
126    /// Files with audio
127    pub fn has_audio(self) -> Self {
128        self.change_name("has audio")
129    }
130
131    /// Files without audio
132    pub fn no_audio(self) -> Self {
133        self.change_name("no audio")
134    }
135
136    /// Files with tags
137    pub fn has_tags(self) -> Self {
138        self.change_name("has tags")
139    }
140
141    /// Files without tags
142    pub fn no_tags(self) -> Self {
143        self.change_name("no tags")
144    }
145
146    /// Untagged files
147    pub fn untagged(self) -> Self {
148        self.change_name("untagged")
149    }
150
151    /// Files with a specific number of tags
152    pub fn number_of_tags(self, comparator: Comparator, value: u64) -> Self {
153        self.change_name(format!("number of tags {} {}", comparator, value))
154    }
155
156    /// Files with a specific height
157    pub fn height(self, comparator: Comparator, value: u64) -> Self {
158        self.change_name(format!("height {} {}", comparator, value))
159    }
160
161    /// Files with a specific width
162    pub fn width(self, comparator: Comparator, value: u64) -> Self {
163        self.change_name(format!("width {} {}", comparator, value))
164    }
165
166    /// Files with a specific filesize
167    pub fn filesize(self, comparator: Comparator, value: u64, unit: FileSizeUnit) -> Self {
168        self.change_name(format!("filesize {} {} {}", comparator, value, unit))
169    }
170
171    /// Files that are similar to a list of other files with a specific [hamming distance](https://en.wikipedia.org/wiki/Hamming_distance)
172    pub fn similar_to(self, hashes: Vec<String>, distance: u32) -> Self {
173        self.change_name(format!(
174            "similar to {} with distance {}",
175            hashes.join(", "),
176            distance
177        ))
178    }
179
180    /// Limit the number of returned files
181    pub fn limit(self, value: u64) -> Self {
182        self.change_name(format!("limit = {}", value))
183    }
184
185    /// Files with a specific mimetype
186    pub fn filetype(self, mimes: Vec<Mime>) -> Self {
187        self.change_name(format!(
188            "filetype = {}",
189            mimes
190                .into_iter()
191                .map(|m| m.to_string())
192                .collect::<Vec<String>>()
193                .join(", ")
194        ))
195    }
196
197    /// Files with a specific hash
198    pub fn hash(self, hashes: Vec<String>) -> Self {
199        self.change_name(format!("hash = {}", hashes.join(" ")))
200    }
201
202    /// Files that have been modified before / after / at / around a specific date and time
203    pub fn date_modified<D: Datelike>(self, comparator: Comparator, datetime: D) -> Self {
204        self.change_name(format!(
205            "modified date {} {}",
206            comparator,
207            format_datetime(datetime)
208        ))
209    }
210
211    /// Files with a specific import time
212    pub fn time_imported<D: Datelike>(self, comparator: Comparator, datetime: D) -> Self {
213        self.change_name(format!(
214            "time imported {} {}",
215            comparator,
216            format_datetime(datetime)
217        ))
218    }
219
220    /// Files that are in a file service or pending to it
221    pub fn file_service(
222        self,
223        comparator: IsComparator,
224        cur_pen: CurrentlyOrPending,
225        service: ServiceName,
226    ) -> Self {
227        self.change_name(format!(
228            "file service {} {} {}",
229            comparator, cur_pen, service
230        ))
231    }
232
233    /// Files that have a specific number of relationships
234    pub fn number_of_relationships(
235        self,
236        comparator: Comparator,
237        value: u64,
238        relationship: FileRelationshipType,
239    ) -> Self {
240        self.change_name(format!(
241            "num file relationships {} {} {}",
242            comparator, value, relationship
243        ))
244    }
245
246    /// Files with a specific aspect ratio
247    pub fn ratio(self, wte: WiderTallerEqual, value: (u64, u64)) -> Self {
248        self.change_name(format!("ratio {} {}:{}", wte, value.0, value.1))
249    }
250
251    /// Files with a specific number of pixels
252    pub fn number_of_pixels(self, comparator: Comparator, value: u64, unit: PixelUnit) -> Self {
253        self.change_name(format!("num pixels {} {} {}", comparator, value, unit))
254    }
255
256    /// Files that have been viewed a specific number of times
257    pub fn views(self, view_type: ViewType, comparator: Comparator, value: u64) -> Self {
258        self.change_name(format!("{} views {} {}", view_type, comparator, value))
259    }
260
261    /// Files that have been viewed for a specific number of times
262    pub fn viewtime(self, view_type: ViewType, comparator: Comparator, duration: Duration) -> Self {
263        self.change_name(format!(
264            "{} viewtime {} {}",
265            view_type,
266            comparator,
267            format_duration(duration)
268        ))
269    }
270
271    /// Files that have associated urls that match a defined regex
272    pub fn has_url_matching_regex<S: Display>(self, regex: S) -> Self {
273        self.change_name(format!("has url matching regex {}", regex))
274    }
275
276    /// Files that don't have an url that matches a defined regex
277    pub fn does_not_have_url_matching_regex<S: Display>(self, regex: S) -> Self {
278        self.change_name(format!("does not have url matching regex {}", regex))
279    }
280
281    /// Files that have an url that matches a class (e.g. 'safebooru file page')
282    pub fn has_url_with_class<S: Display>(self, class: S) -> Self {
283        self.change_name(format!("has url with class {}", class))
284    }
285
286    /// Files that don't have an url that matches a class (e.g. 'safebooru file page')
287    pub fn does_not_have_url_with_class<S: Display>(self, class: S) -> Self {
288        self.change_name(format!("does not have url with class {}", class))
289    }
290
291    /// Converts a tag namespace (e.g. 'page') into a number and compares it
292    pub fn tag_namespace_as_number<S: Display>(
293        self,
294        namespace: S,
295        comparator: Comparator,
296        value: u64,
297    ) -> Self {
298        self.change_name(format!(
299            "tag as number {} {} {}",
300            namespace, comparator, value
301        ))
302    }
303
304    fn change_name<S: ToString>(mut self, value: S) -> Self {
305        self.name = value.to_string();
306
307        self
308    }
309}
310
311#[derive(Clone, Debug, PartialOrd, PartialEq)]
312pub enum Comparator {
313    /// rhs > lhs
314    Greater,
315    /// rhs < lhs
316    Less,
317    /// rhs == lhs
318    Equal,
319    /// If the rhs is in a +-15% range of the lhs
320    Approximate,
321}
322
323impl Display for Comparator {
324    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
325        let symbol = match self {
326            Comparator::Greater => ">",
327            Comparator::Less => "<",
328            Comparator::Equal => "=",
329            Comparator::Approximate => "~=",
330        };
331        symbol.fmt(f)
332    }
333}
334
335#[derive(Clone, Debug, PartialOrd, PartialEq)]
336pub enum FileSizeUnit {
337    Bytes,
338    Kilobytes,
339    Megabytes,
340    Gigabytes,
341}
342
343impl Display for FileSizeUnit {
344    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
345        let name = match self {
346            FileSizeUnit::Bytes => "B",
347            FileSizeUnit::Kilobytes => "KB",
348            FileSizeUnit::Megabytes => "MB",
349            FileSizeUnit::Gigabytes => "GB",
350        };
351        name.fmt(f)
352    }
353}
354
355#[derive(Clone, Debug, PartialOrd, PartialEq)]
356pub enum DurationUnit {
357    Hours,
358    Minutes,
359    Seconds,
360    Milliseconds,
361}
362
363impl Display for DurationUnit {
364    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
365        let name = match self {
366            DurationUnit::Hours => "hours",
367            DurationUnit::Minutes => "minutes",
368            DurationUnit::Seconds => "seconds",
369            DurationUnit::Milliseconds => "milliseconds",
370        };
371        name.fmt(f)
372    }
373}
374
375#[derive(Clone, Debug, PartialOrd, PartialEq)]
376pub enum IsComparator {
377    Is,
378    IsNot,
379}
380
381impl Display for IsComparator {
382    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
383        let name = match self {
384            IsComparator::Is => "is",
385            IsComparator::IsNot => "is not",
386        };
387        name.fmt(f)
388    }
389}
390
391#[derive(Clone, Debug, PartialOrd, PartialEq)]
392pub enum CurrentlyOrPending {
393    CurrentlyIn,
394    PendingTo,
395}
396
397impl Display for CurrentlyOrPending {
398    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
399        let name = match self {
400            CurrentlyOrPending::CurrentlyIn => "currently in",
401            CurrentlyOrPending::PendingTo => "pending to",
402        };
403        name.fmt(f)
404    }
405}
406
407#[derive(Clone, Debug, PartialOrd, PartialEq)]
408pub enum WiderTallerEqual {
409    Wider,
410    Taller,
411    Equal,
412}
413
414impl Display for WiderTallerEqual {
415    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
416        let name = match self {
417            WiderTallerEqual::Wider => "wider than",
418            WiderTallerEqual::Taller => "taller than",
419            WiderTallerEqual::Equal => "is",
420        };
421        name.fmt(f)
422    }
423}
424
425#[derive(Clone, Debug, PartialOrd, PartialEq)]
426pub enum PixelUnit {
427    Pixels,
428    Kilopixels,
429    Megapixels,
430}
431
432impl Display for PixelUnit {
433    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
434        let name = match self {
435            PixelUnit::Pixels => "pixels",
436            PixelUnit::Kilopixels => "kilopixels",
437            PixelUnit::Megapixels => "megapixels",
438        };
439        name.fmt(f)
440    }
441}
442
443#[derive(Clone, Debug, PartialOrd, PartialEq)]
444pub enum ViewType {
445    Media,
446    Preview,
447    All,
448}
449
450impl Display for ViewType {
451    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
452        let name = match self {
453            ViewType::Media => "media",
454            ViewType::Preview => "preview",
455            ViewType::All => "all",
456        };
457        name.fmt(f)
458    }
459}
460
461#[derive(Clone, Debug, PartialOrd, PartialEq)]
462pub enum FileRelationshipType {
463    Alternates,
464    FalsePositives,
465    Duplicates,
466    PotentialDuplicates,
467}
468
469impl Display for FileRelationshipType {
470    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
471        let name = match self {
472            FileRelationshipType::Alternates => "alternates",
473            FileRelationshipType::FalsePositives => "false positives",
474            FileRelationshipType::Duplicates => "duplicates",
475            FileRelationshipType::PotentialDuplicates => "potential duplicates",
476        };
477        name.fmt(f)
478    }
479}