diff --git a/Cargo.lock b/Cargo.lock index 07fa2b4..ce66b8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -477,6 +477,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -779,6 +794,19 @@ dependencies = [ "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.59" @@ -852,6 +880,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "lazy_static" version = "1.4.0" @@ -971,8 +1005,10 @@ dependencies = [ "anyhow", "chrono", "dotenv", + "json", "rand", "regex", + "reqwest", "serenity 0.12.0", "serenity_utils", "sqlx", @@ -980,6 +1016,24 @@ dependencies = [ "tokio-cron-scheduler", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.27.1" @@ -1084,6 +1138,50 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-float" version = "2.10.1" @@ -1276,9 +1374,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ "base64 0.21.7", "bytes", @@ -1290,11 +1388,13 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1303,8 +1403,10 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls 0.24.1", "tokio-util", "tower-service", @@ -1440,6 +1542,15 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1466,6 +1577,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.195" @@ -1929,6 +2063,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "system-configuration" version = "0.5.1" @@ -2069,6 +2209,16 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -2415,9 +2565,9 @@ checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "wasm-streams" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" dependencies = [ "futures-util", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 9fb77ef..cd0b109 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] } regex = "1.9.0" chrono = "0.4.31" sqlx = {version="0.7.3", features=["runtime-tokio", "sqlite"]} +json = "0.12.4" +reqwest = "0.11.24" [features] DEBUG = [] diff --git a/src/other/mod.rs b/src/other/mod.rs index 97abf42..992a442 100644 --- a/src/other/mod.rs +++ b/src/other/mod.rs @@ -1 +1,4 @@ -pub mod notice; \ No newline at end of file +pub mod notice; +pub mod tenor_builder; +pub mod tenor_types; +pub mod tenor; \ No newline at end of file diff --git a/src/other/tenor_builder.rs b/src/other/tenor_builder.rs new file mode 100644 index 0000000..047aa40 --- /dev/null +++ b/src/other/tenor_builder.rs @@ -0,0 +1,161 @@ +use std::{collections::hash_map::RandomState, fmt}; + +use json::JsonValue; + +use std::collections::HashSet; + +use anyhow::Error; + +use crate::util::security::dotenv_var; + +use super::tenor_types::{ContentFilter, ArRange, MediaFilter}; + +pub struct Tenor { + /// Specify the country of origin for the request. To do so, provide its two-letter ISO 3166-1 country code. + country: Option, + /// Specify the default language to interpret the search string using ISO 639-1 language code + optional two-letter ISO 3166-1 country code. + /// You can use the country code that you provide in locale to differentiate between dialects of the given language. + locale: Option, + /// Specify the content safety filter level + /// The default value is off + contentfilter: Option, + /// Set of GIF formats to filter + /// By default returns all formats + media_filter: Option>, + /// Filter to only include GIFS with the aspect ratios that fit within range + /// Default value is all + /// wide: 0.42 <= aspect ratio <= 2.36 + /// standard: 0.56 <= aspect ratio <= 1.78 + ar_range: Option, + /// Whether to randomly order the response + /// Default value is false + random: Option, + /// Fetch up to the specified number of results + /// Default value (for Tenor) is 20 and maximum is 50 + /// if smaller or greater values is provided, the value is clamped to the min/max + limit: Option, + /// Retrieve results that start at the position + /// Use a non-zero, non-empty value from next, returned by the API response, to get the next set of results + pos: Option +} + +impl Tenor { + fn new(q: String) -> Self { + Tenor { + country: None, + locale: None, + contentfilter: None, + media_filter: None, + ar_range: None, + random: None, + limit: None, + pos: None + } + } + + /// Replaces current country with the passed one + fn country(mut self, country: String) -> Self { + self.country = Some(country); + self + } + + /// Replaces current locale with the passed one + fn locale(mut self, locale: String) -> Self { + self.locale = Some(locale); + self + } + + /// Replaces current media_filter with the passed one + fn media_filter(mut self, filter: HashSet) -> Self { + self.media_filter = Some(filter); + self + } + + /// Replaces current contentfilter with the passed one + fn contentfilter(mut self, filter: ContentFilter) -> Self { + self.contentfilter = Some(filter); + self + } + + /// Replaces current media_filter with the passed one + fn add_media_filter(mut self, filter: MediaFilter) -> Self { + if self.media_filter.is_none() { + let mut set = HashSet::new(); + set.insert(filter); + self.media_filter = Some(set); + } + else { + self.media_filter.as_mut().unwrap().insert(filter); + } + self + } + + fn ar_range(mut self, range: ArRange) -> Self { + self.ar_range = Some(range); + self + } + + fn random(mut self, random: bool) -> Self { + self.random = Some(random); + self + } + + fn limit(mut self, mut limit: u8) -> Self { + if limit < 20 { + limit = 20; + } + else if limit > 50 { + limit = 50; + } + + self.limit = Some(limit); + self + } + + fn pos(mut self, pos: String) -> Self { + self.pos = Some(pos); + self + } + + async fn search(self, query: String) -> Result { + use anyhow::Context; + + // TODO encode query for urls (replace special characters and stuff) + let base_url = "https://tenor.googleapis.com/v2/search?"; + let api_key = dotenv_var("TENORV2").context("TENORV2 key not found in the .env")?; + + let mut request = format!("{base_url}q={query}&key={api_key}"); + + if self.country.is_some() { + request.push_str(&format!("&country={}", self.country.unwrap())); + } + + if self.locale.is_some() { + request.push_str(&format!("&locale={}", self.locale.unwrap())); + } + + if self.media_filter.is_some() { + // TODO implement + // self.media_filter_to_string() + // request.push_str(); + } + + if self.ar_range.is_some() { + request.push_str(&format!("&ar_range={}", self.ar_range.unwrap())); + } + + if self.random.is_some() { + request.push_str(&format!("&random={}", self.random.unwrap())); + } + + if self.limit.is_some() { + request.push_str(&format!("&limit={}", self.limit.unwrap())); + } + + let response = reqwest::get(request).await?.text().await?; + + let response_json = json::parse(&response)?; + + Ok(response_json) + } +} \ No newline at end of file diff --git a/src/other/tenor_types.rs b/src/other/tenor_types.rs new file mode 100644 index 0000000..f20e29d --- /dev/null +++ b/src/other/tenor_types.rs @@ -0,0 +1,64 @@ +#![allow(non_camel_case_types)] + +use std::fmt; + + +#[derive(Debug)] +pub enum ContentFilter { + off, + low, + medium, + high +} + +impl fmt::Display for ContentFilter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +/// More information here https://developers.google.com/tenor/guides/response-objects-and-errors#media-object +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum MediaFilter { + preview, + gif, + mediumgif, + tinygif, + nanogif, + + mp4, + loopedmp4, + tinymp4, + nanomp4, + + webm, + tinywebm, + nanowebm, + + webp_transparent, + tinywebp_transparent, + nanowebp_transparent, + + gif_transparent, + tinygif_transparent, + nanogif_transparent +} + +impl fmt::Display for MediaFilter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Debug)] +pub enum ArRange { + all, + wide, + standard +} + +impl fmt::Display for ArRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} \ No newline at end of file