diff --git a/tenor_v2rs/src/lib.rs b/tenor_v2rs/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/tenor_v2rs/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/tenor_v2rs/src/tenor.rs b/tenor_v2rs/src/tenor.rs new file mode 100644 index 0000000..6ac9049 --- /dev/null +++ b/tenor_v2rs/src/tenor.rs @@ -0,0 +1,17 @@ +use super::tenor_types::MediaFilter; + +use json::JsonValue; + +pub fn get_gif_url(filter: MediaFilter, tenor_response: JsonValue) -> anyhow::Result> { + use anyhow::Context; + + let mut urls: Vec = Vec::new(); + let results = tenor_response["results"].members(); + + for result in results { + let url = result["media_formats"][filter.to_string()]["url"].as_str().context("Value not found in Json")?; + urls.push(url.to_string()); + } + + Ok(urls) +} \ No newline at end of file diff --git a/tenor_v2rs/src/tenor_builder.rs b/tenor_v2rs/src/tenor_builder.rs new file mode 100644 index 0000000..4f0de5a --- /dev/null +++ b/tenor_v2rs/src/tenor_builder.rs @@ -0,0 +1,171 @@ +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 { + pub fn new() -> 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 + pub fn country(mut self, country: String) -> Self { + self.country = Some(country); + self + } + + /// Replaces current locale with the passed one + pub fn locale(mut self, locale: String) -> Self { + self.locale = Some(locale); + self + } + + /// Replaces current media_filter with the passed one + pub fn media_filter(mut self, filter: HashSet) -> Self { + self.media_filter = Some(filter); + self + } + + /// Replaces current contentfilter with the passed one + pub fn contentfilter(mut self, filter: ContentFilter) -> Self { + self.contentfilter = Some(filter); + self + } + + /// Replaces current media_filter with the passed one + pub 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 + } + + pub fn ar_range(mut self, range: ArRange) -> Self { + self.ar_range = Some(range); + self + } + + pub fn random(mut self, random: bool) -> Self { + self.random = Some(random); + self + } + + pub fn limit(mut self, mut limit: u8) -> Self { + if limit < 20 { + limit = 20; + } + else if limit > 50 { + limit = 50; + } + + self.limit = Some(limit); + self + } + + pub fn pos(mut self, pos: String) -> Self { + self.pos = Some(pos); + self + } + + pub async fn search(self, query: &str) -> Result { + use anyhow::Context; + + let q: String = form_urlencoded::byte_serialize(query.as_bytes()).collect(); + + 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={q}&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.contentfilter.is_some() { + request.push_str(&format!("&contentfilter={}", self.contentfilter.unwrap())) + } + + if self.media_filter.is_some() && self.media_filter.as_ref().unwrap().len() > 0 { + request.push_str( + format!("&media_filter={}", + self.media_filter.unwrap() + .iter() + .map(|e| e.to_string()) + .collect::>() + .join(",") + .as_str() + ).as_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/tenor_v2rs/src/tenor_types.rs b/tenor_v2rs/src/tenor_types.rs new file mode 100644 index 0000000..91620e5 --- /dev/null +++ b/tenor_v2rs/src/tenor_types.rs @@ -0,0 +1,63 @@ +#![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