Add gifs table and matsuri, add_gif and gifs command

Added a gif table to store gifs with optional category and name
as. The command add_gif (owner only) can be used to add a gif to
the database. The command gifs can be used to see all the gifs
stored in the database. The gifs are being used by the pekofy and
matsuri command.

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/10/head
trivernis 3 years ago
parent 1bbbdc29f6
commit a67fecc210
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

4
Cargo.lock generated

@ -180,7 +180,7 @@ dependencies = [
[[package]]
name = "bot-coreutils"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"log 0.4.14",
"mime_guess",
@ -191,7 +191,7 @@ dependencies = [
[[package]]
name = "bot-database"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"chrono",
"diesel",

@ -8,7 +8,7 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bot-coreutils"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"log",
"mime_guess",

@ -1,6 +1,6 @@
[package]
name = "bot-coreutils"
version = "0.1.0"
version = "0.1.1"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"

@ -0,0 +1,9 @@
use rand::seq::SliceRandom;
/// Chooses a random value from the given iterator
/// panics when the iterator is empty
pub fn choose_unchecked<'a, I: SliceRandom<Item = &'a T>, T>(i: I) -> &'a T {
let mut rng = rand::thread_rng();
i.choose(&mut rng).unwrap()
}

@ -35,3 +35,10 @@ fn it_checks_for_video() {
assert!(!is_video("https://domain.com/file.pdf"));
assert!(!is_video("not an url"));
}
#[test]
fn it_checks_if_its_valid() {
assert!(is_valid("https://domain.com"));
assert!(!is_valid("domain.com"));
assert!(is_valid("https://url.com/sub/sub/sub.txt"))
}

@ -69,3 +69,8 @@ pub fn is_video(url_str: &str) -> bool {
false
}
}
/// Returns if the given url is valid
pub fn is_valid(url_str: &str) -> bool {
Url::parse(url_str).is_ok()
}

@ -1,6 +1,6 @@
[package]
name = "bot-database"
version = "0.1.0"
version = "0.2.0"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE gifs;

@ -0,0 +1,8 @@
-- Your SQL goes here
CREATE TABLE gifs (
id BIGSERIAL PRIMARY KEY ,
category VARCHAR(128),
name VARCHAR(128),
url VARCHAR(128) NOT NULL,
UNIQUE (category, name)
)

@ -152,4 +152,51 @@ impl Database {
Ok(())
}
/// Returns a list of all gifs in the database
pub async fn get_all_gifs(&self) -> DatabaseResult<Vec<Gif>> {
use gifs::dsl;
log::debug!("Loading all gifs from the database");
let gifs: Vec<Gif> = dsl::gifs.load_async::<Gif>(&self.pool).await?;
Ok(gifs)
}
/// Returns a list of gifs by assigned category
pub async fn get_gifs_by_category(&self, category: &str) -> DatabaseResult<Vec<Gif>> {
use gifs::dsl;
log::debug!("Searching for gifs in category '{}'", category);
let gifs: Vec<Gif> = dsl::gifs
.filter(dsl::category.eq(category))
.load_async::<Gif>(&self.pool)
.await?;
Ok(gifs)
}
/// Adds a gif to the database
pub async fn add_gif(
&self,
url: &str,
category: Option<String>,
name: Option<String>,
) -> DatabaseResult<()> {
use gifs::dsl;
log::debug!(
"Inserting gif with url '{}' and name {:?} and category {:?}",
url,
name,
category
);
insert_into(dsl::gifs)
.values(GifInsert {
url: url.to_string(),
name,
category,
})
.execute_async(&self.pool)
.await?;
Ok(())
}
}

@ -29,3 +29,19 @@ pub struct GuildPlaylistInsert {
pub name: String,
pub url: String,
}
#[derive(Queryable, Debug, Clone)]
pub struct Gif {
pub id: i64,
pub category: Option<String>,
pub name: Option<String>,
pub url: String,
}
#[derive(Insertable, Debug)]
#[table_name = "gifs"]
pub struct GifInsert {
pub category: Option<String>,
pub name: Option<String>,
pub url: String,
}

@ -1,3 +1,12 @@
table! {
gifs (id) {
id -> Int8,
category -> Nullable<Varchar>,
name -> Nullable<Varchar>,
url -> Varchar,
}
}
table! {
guild_playlists (guild_id, name) {
guild_id -> Int8,
@ -15,6 +24,7 @@ table! {
}
allow_tables_to_appear_in_same_query!(
gifs,
guild_playlists,
guild_settings,
);

@ -0,0 +1,31 @@
use crate::utils::context_data::get_database_from_context;
use bot_coreutils::url;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
#[command]
#[description("Simple ping test command")]
#[usage("<url> (<category>) (<name>)")]
#[bucket("general")]
#[aliases("add-gif", "addgif")]
#[min_args(1)]
#[max_args(3)]
#[owners_only]
async fn add_gif(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let url = args.single::<String>()?;
if !url::is_valid(&url) {
msg.reply(ctx, "Invalid url").await?;
return Ok(());
}
let category = args.single_quoted::<String>().ok();
let name = args.single_quoted::<String>().ok();
let database = get_database_from_context(&ctx).await;
database.add_gif(&url, category, name).await?;
msg.reply(ctx, "Gif added to database").await?;
Ok(())
}

@ -0,0 +1,17 @@
use crate::messages::gifs::create_gifs_menu;
use crate::utils::context_data::get_database_from_context;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message;
#[command]
#[description("Displays a list of all gifs used by the bot")]
#[bucket("general")]
async fn gifs(ctx: &Context, msg: &Message) -> CommandResult {
let database = get_database_from_context(ctx).await;
let gifs = database.get_all_gifs().await?;
create_gifs_menu(ctx, msg.channel_id, gifs).await?;
Ok(())
}

@ -1,6 +1,8 @@
use serenity::framework::standard::macros::group;
use about::ABOUT_COMMAND;
use add_gif::ADD_GIF_COMMAND;
use gifs::GIFS_COMMAND;
use ping::PING_COMMAND;
use qalc::QALC_COMMAND;
use shutdown::SHUTDOWN_COMMAND;
@ -9,6 +11,8 @@ use time::TIME_COMMAND;
use timezones::TIMEZONES_COMMAND;
mod about;
mod add_gif;
mod gifs;
pub(crate) mod help;
mod ping;
mod qalc;
@ -18,5 +22,5 @@ mod time;
mod timezones;
#[group]
#[commands(ping, stats, shutdown, time, timezones, qalc, about)]
#[commands(ping, stats, shutdown, time, timezones, qalc, about, add_gif, gifs)]
pub struct Misc;

@ -0,0 +1,26 @@
use crate::utils::context_data::get_database_from_context;
use crate::utils::error::BotError;
use rand::prelude::IteratorRandom;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message;
static GIF_CATEGORY: &str = "matsuri";
#[command]
#[description("Posts a random matsuri gif")]
#[usage("")]
#[bucket("general")]
async fn matsuri(ctx: &Context, msg: &Message) -> CommandResult {
let database = get_database_from_context(ctx).await;
let gifs = database.get_gifs_by_category(GIF_CATEGORY).await?;
let gif = gifs
.into_iter()
.choose(&mut rand::thread_rng())
.ok_or(BotError::from("No gifs found."))?;
msg.channel_id.say(ctx, gif.url).await?;
Ok(())
}

@ -1,11 +1,13 @@
use serenity::framework::standard::macros::group;
mod matsuri;
mod pekofy;
mod sauce;
use matsuri::MATSURI_COMMAND;
use pekofy::PEKOFY_COMMAND;
use sauce::SAUCE_COMMAND;
#[group]
#[commands(pekofy, sauce)]
#[commands(pekofy, sauce, matsuri)]
pub struct Weeb;

@ -4,7 +4,10 @@ use serenity::framework::standard::{Args, CommandError, CommandResult};
use serenity::model::channel::Message;
use serenity::{framework::standard::macros::command, prelude::*};
use crate::utils::context_data::get_database_from_context;
use crate::utils::error::{BotError, BotResult};
use crate::utils::get_previous_message_or_reply;
use bot_database::models::Gif;
// return a normal peko in most cases
static PEKOS: &[&str] = &[
@ -15,6 +18,7 @@ static PEKOS: &[&str] = &[
"🇵 🇪 🇰 🇴",
"p3k0",
];
static GIF_CATEGORY: &str = "pain-peko";
#[command]
#[description("Pekofy messages")]
@ -44,7 +48,7 @@ async fn pekofy(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
alpha_lowercase.retain(|c| c.is_alphanumeric());
let pekofied: String = if alpha_lowercase == "pain" {
"https://tenor.com/view/pekora-usada-peko-hololive-died-gif-18114577".to_string()
random_pain_gif(ctx).await?.url
} else if PEKOS.contains(&&*alpha_lowercase) {
random_peko()
} else {
@ -108,3 +112,12 @@ fn random_peko() -> String {
"peko".to_string()
}
}
/// Chooses a random pain peko gif
async fn random_pain_gif(ctx: &Context) -> BotResult<Gif> {
let database = get_database_from_context(ctx).await;
let gifs = database.get_gifs_by_category(GIF_CATEGORY).await?;
gifs.into_iter()
.choose(&mut rand::thread_rng())
.ok_or(BotError::from("No gifs found"))
}

@ -0,0 +1,52 @@
use crate::utils::error::BotResult;
use bot_database::models::Gif;
use bot_serenityutils::menu::{MenuBuilder, Page};
use serenity::builder::CreateMessage;
use serenity::client::Context;
use serenity::model::id::ChannelId;
use std::time::Duration;
/// Creates a new gifs embed
pub async fn create_gifs_menu(
ctx: &Context,
channel_id: ChannelId,
gifs: Vec<Gif>,
) -> BotResult<()> {
let total_pages = (gifs.len() as f32 / 10.0).ceil() as usize;
let pages: Vec<Page> = gifs
.chunks(10)
.enumerate()
.map(|(page, gifs)| create_gifs_page(page + 1, total_pages, gifs.to_vec()))
.collect();
MenuBuilder::new_paginator()
.timeout(Duration::from_secs(120))
.add_pages(pages)
.build(ctx, channel_id)
.await?;
Ok(())
}
/// Creates a new gif page
pub fn create_gifs_page(page: usize, total_pages: usize, gifs: Vec<Gif>) -> Page<'static> {
let mut message = CreateMessage::default();
let description_lines: Vec<String> = gifs
.into_iter()
.map(|g| {
format!(
"{} - {} - [Source]({})",
g.category.unwrap_or("*N/A*".to_string()),
g.name.unwrap_or("*N/A*".to_string()),
g.url
)
})
.collect();
message.embed(|e| {
e.title("Gifs")
.description(description_lines.join("\n"))
.footer(|f| f.text(format!("Page {} of {}", page, total_pages)))
});
Page::new_static(message)
}

@ -1,2 +1,3 @@
pub mod gifs;
pub mod music;
pub mod sauce;

Loading…
Cancel
Save