From 7303d35633e0261b08f68f8d52e509ae313df2cd Mon Sep 17 00:00:00 2001 From: Ladislav Hano <524934@mail.muni.cz> Date: Sat, 17 Feb 2024 18:49:13 +0100 Subject: [PATCH] Created Tenor struct for Tenor api calls --- Cargo.toml | 2 + src/other/mod.rs | 5 +- src/other/tenor_builder.rs | 161 +++++++++++++++++++++++++++++++++++++ src/other/tenor_types.rs | 64 +++++++++++++++ 4 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 src/other/tenor_builder.rs create mode 100644 src/other/tenor_types.rs 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