basic gifs implementation

This commit is contained in:
Ladislav Hano 2024-02-18 12:14:02 +01:00
parent 4be8cd231f
commit 8949409947
7 changed files with 90 additions and 38 deletions

1
Cargo.lock generated
View file

@ -1005,6 +1005,7 @@ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
"dotenv", "dotenv",
"form_urlencoded",
"json", "json",
"rand", "rand",
"regex", "regex",

View file

@ -19,6 +19,7 @@ chrono = "0.4.31"
sqlx = {version="0.7.3", features=["runtime-tokio", "sqlite"]} sqlx = {version="0.7.3", features=["runtime-tokio", "sqlite"]}
json = "0.12.4" json = "0.12.4"
reqwest = "0.11.24" reqwest = "0.11.24"
form_urlencoded = "1.2.1"
[features] [features]
DEBUG = [] DEBUG = []

View file

@ -14,8 +14,6 @@ use message_handler::handle;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
// use chrono::Utc;
mod commands; mod commands;
mod util; mod util;
@ -37,7 +35,7 @@ impl EventHandler for Handler {
use util::debug::hello; use util::debug::hello;
hello(ctx.http.clone()).await; hello(ctx.http.clone()).await;
} }
let sched = JobScheduler::new().await.unwrap(); let sched = JobScheduler::new().await.unwrap();
let job_closure = move |_, _| -> Pin<Box<dyn Future<Output = ()> + Send>> { let job_closure = move |_, _| -> Pin<Box<dyn Future<Output = ()> + Send>> {

View file

@ -2,7 +2,7 @@ use chrono::{Datelike, Local};
use serenity::{all::{GuildId, UserId}, builder::{CreateEmbed, CreateMessage}, client::Context, http::Http, model::Colour}; use serenity::{all::{GuildId, UserId}, builder::{CreateEmbed, CreateMessage}, client::Context, http::Http, model::Colour};
use anyhow::Ok; // use anyhow::Ok;
use sqlx::{Connection, FromRow, SqliteConnection}; use sqlx::{Connection, FromRow, SqliteConnection};
@ -12,6 +12,8 @@ use crate::util::utilities;
use std::sync::Arc; use std::sync::Arc;
use super::{tenor, tenor_builder::Tenor, tenor_types::{ContentFilter, MediaFilter}};
// pub async fn notice_wrapper(http: Arc<Http>) { // pub async fn notice_wrapper(http: Arc<Http>) {
pub async fn notice_wrapper(ctx: Context) { pub async fn notice_wrapper(ctx: Context) {
match notice(ctx.http.clone()).await { match notice(ctx.http.clone()).await {
@ -24,6 +26,7 @@ pub async fn notice_wrapper(ctx: Context) {
} }
async fn announce_event(guild_id: GuildId, name: &str, special_message: &str, http: Arc<Http>) -> anyhow::Result<()> { async fn announce_event(guild_id: GuildId, name: &str, special_message: &str, http: Arc<Http>) -> anyhow::Result<()> {
let mut event_embed = CreateEmbed::new() let mut event_embed = CreateEmbed::new()
.color(Colour::new(rand::random::<u32>() % 0xFFFFFF)) .color(Colour::new(rand::random::<u32>() % 0xFFFFFF))
.title("Today's event is:"); .title("Today's event is:");
@ -45,13 +48,35 @@ async fn announce_event(guild_id: GuildId, name: &str, special_message: &str, ht
} }
async fn celebrate_birthday(guild_id: GuildId, user_id: UserId, nick: &str, http: Arc<Http>) -> anyhow::Result<()> { async fn celebrate_birthday(guild_id: GuildId, user_id: UserId, nick: &str, http: Arc<Http>) -> anyhow::Result<()> {
const LIMIT: u8 = 20;
let tenor_response = Tenor::new()
.random(true)
.contentfilter(ContentFilter::low)
.add_media_filter(MediaFilter::gif)
.search("vsetko najlepsie").await?;
let index = rand::random::<usize>() % LIMIT as usize;
let gif_url = match tenor::get_gif_url(MediaFilter::gif, tenor_response) {
Ok(urls) => Some(urls),
Err(e) => {
send_error(http.clone(), e.to_string()).await;
None
}
};
let system_channel = utilities::get_system_channel(guild_id, http.clone()).await?; let system_channel = utilities::get_system_channel(guild_id, http.clone()).await?;
let embed = CreateEmbed::new() let mut embed = CreateEmbed::new()
.color(Colour::new(rand::random::<u32>() % 0xFFFFFF)) .color(Colour::new(rand::random::<u32>() % 0xFFFFFF))
.title(format!("HAPPY BIRTHDAY {}!", nick)) .title(format!("HAPPY BIRTHDAY {}!", nick))
.description(format!("<@{}>'s birthday is today!!! Yay!", user_id.get())); .description(format!("<@{}>'s birthday is today!!! Yay!", user_id.get()));
if gif_url.is_some() {
embed = embed.image(gif_url.unwrap()[index].as_str());
}
system_channel.send_message(http.clone(), CreateMessage::new().add_embed(embed.clone())).await?; system_channel.send_message(http.clone(), CreateMessage::new().add_embed(embed.clone())).await?;
Ok(()) Ok(())
@ -90,19 +115,19 @@ async fn notice(http: Arc<Http>) -> anyhow::Result<()> {
"SELECT id, nick FROM birthdays "SELECT id, nick FROM birthdays
WHERE day=? AND month=?;" WHERE day=? AND month=?;"
) )
.bind(day) .bind(day)
.bind(month) .bind(month)
.fetch_all(&mut db) .fetch_all(&mut db)
.await?; .await?;
let global_events = sqlx::query_as::<_, EventRow>( let global_events = sqlx::query_as::<_, EventRow>(
"SELECT id, guild, name, year, special_message from events "SELECT id, guild, name, year, special_message from events
WHERE day=? AND month=? AND guild=0;" WHERE day=? AND month=? AND guild=0;"
) )
.bind(day) .bind(day)
.bind(month) .bind(month)
.fetch_all(&mut db) .fetch_all(&mut db)
.await?; .await?;
let guilds = http.get_guilds(None, None).await?; let guilds = http.get_guilds(None, None).await?;
@ -127,18 +152,18 @@ async fn notice(http: Arc<Http>) -> anyhow::Result<()> {
} }
} }
let global_events = sqlx::query_as::<_, EventRow>( let guild_events = sqlx::query_as::<_, EventRow>(
"SELECT id, guild, name, year, special_message from events "SELECT id, guild, name, year, special_message from events
WHERE day=? AND month=? AND guild!=0;" WHERE day=? AND month=? AND guild!=0;"
) )
.bind(day) .bind(day)
.bind(month) .bind(month)
.fetch_all(&mut db) .fetch_all(&mut db)
.await?; .await?;
// TODO if has year delete it from announce and delete // TODO if has year delete it from announce and delete
for e in &global_events { for e in &guild_events {
announce_event(GuildId::new(e.guild), e.name.as_str(), e.special_message.as_str(), http.clone()).await?; announce_event(GuildId::new(e.guild), e.name.as_str(), e.special_message.as_str(), http.clone()).await?;
} }

View file

@ -0,0 +1,17 @@
use super::tenor_types::MediaFilter;
use json::JsonValue;
pub fn get_gif_url(filter: MediaFilter, tenor_response: JsonValue) -> anyhow::Result<Vec<String>> {
use anyhow::Context;
let mut urls: Vec<String> = 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)
}

View file

@ -1,5 +1,3 @@
use std::{collections::hash_map::RandomState, fmt};
use json::JsonValue; use json::JsonValue;
use std::collections::HashSet; use std::collections::HashSet;
@ -40,7 +38,7 @@ pub struct Tenor {
} }
impl Tenor { impl Tenor {
fn new(q: String) -> Self { pub fn new() -> Self {
Tenor { Tenor {
country: None, country: None,
locale: None, locale: None,
@ -54,31 +52,31 @@ impl Tenor {
} }
/// Replaces current country with the passed one /// Replaces current country with the passed one
fn country(mut self, country: String) -> Self { pub fn country(mut self, country: String) -> Self {
self.country = Some(country); self.country = Some(country);
self self
} }
/// Replaces current locale with the passed one /// Replaces current locale with the passed one
fn locale(mut self, locale: String) -> Self { pub fn locale(mut self, locale: String) -> Self {
self.locale = Some(locale); self.locale = Some(locale);
self self
} }
/// Replaces current media_filter with the passed one /// Replaces current media_filter with the passed one
fn media_filter(mut self, filter: HashSet<MediaFilter>) -> Self { pub fn media_filter(mut self, filter: HashSet<MediaFilter>) -> Self {
self.media_filter = Some(filter); self.media_filter = Some(filter);
self self
} }
/// Replaces current contentfilter with the passed one /// Replaces current contentfilter with the passed one
fn contentfilter(mut self, filter: ContentFilter) -> Self { pub fn contentfilter(mut self, filter: ContentFilter) -> Self {
self.contentfilter = Some(filter); self.contentfilter = Some(filter);
self self
} }
/// Replaces current media_filter with the passed one /// Replaces current media_filter with the passed one
fn add_media_filter(mut self, filter: MediaFilter) -> Self { pub fn add_media_filter(mut self, filter: MediaFilter) -> Self {
if self.media_filter.is_none() { if self.media_filter.is_none() {
let mut set = HashSet::new(); let mut set = HashSet::new();
set.insert(filter); set.insert(filter);
@ -90,17 +88,17 @@ impl Tenor {
self self
} }
fn ar_range(mut self, range: ArRange) -> Self { pub fn ar_range(mut self, range: ArRange) -> Self {
self.ar_range = Some(range); self.ar_range = Some(range);
self self
} }
fn random(mut self, random: bool) -> Self { pub fn random(mut self, random: bool) -> Self {
self.random = Some(random); self.random = Some(random);
self self
} }
fn limit(mut self, mut limit: u8) -> Self { pub fn limit(mut self, mut limit: u8) -> Self {
if limit < 20 { if limit < 20 {
limit = 20; limit = 20;
} }
@ -112,19 +110,21 @@ impl Tenor {
self self
} }
fn pos(mut self, pos: String) -> Self { pub fn pos(mut self, pos: String) -> Self {
self.pos = Some(pos); self.pos = Some(pos);
self self
} }
async fn search(self, query: String) -> Result<JsonValue, Error> { pub async fn search(self, query: &str) -> Result<JsonValue, Error> {
use anyhow::Context; use anyhow::Context;
let q: String = form_urlencoded::byte_serialize(query.as_bytes()).collect();
// TODO encode query for urls (replace special characters and stuff) // TODO encode query for urls (replace special characters and stuff)
let base_url = "https://tenor.googleapis.com/v2/search?"; let base_url = "https://tenor.googleapis.com/v2/search?";
let api_key = dotenv_var("TENORV2").context("TENORV2 key not found in the .env")?; 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}"); let mut request = format!("{base_url}q={q}&key={api_key}");
if self.country.is_some() { if self.country.is_some() {
request.push_str(&format!("&country={}", self.country.unwrap())); request.push_str(&format!("&country={}", self.country.unwrap()));
@ -134,10 +134,21 @@ impl Tenor {
request.push_str(&format!("&locale={}", self.locale.unwrap())); request.push_str(&format!("&locale={}", self.locale.unwrap()));
} }
if self.media_filter.is_some() { if self.contentfilter.is_some() {
// TODO implement request.push_str(&format!("&contentfilter={}", self.contentfilter.unwrap()))
// self.media_filter_to_string() }
// request.push_str();
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::<Vec<_>>()
.join(",")
.as_str()
).as_str()
);
} }
if self.ar_range.is_some() { if self.ar_range.is_some() {

View file

@ -2,7 +2,6 @@
use std::fmt; use std::fmt;
#[derive(Debug)] #[derive(Debug)]
pub enum ContentFilter { pub enum ContentFilter {
off, off,