Add movie commands
This commit is contained in:
parent
283c09bd1b
commit
d06686dd4a
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -1093,7 +1093,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fren"
|
name = "fren"
|
||||||
version = "1.5.0"
|
version = "1.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.8.1",
|
"axum 0.8.1",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
@ -1120,6 +1120,7 @@ dependencies = [
|
|||||||
"structopt",
|
"structopt",
|
||||||
"symphonia",
|
"symphonia",
|
||||||
"tera",
|
"tera",
|
||||||
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tonic",
|
"tonic",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fren"
|
name = "fren"
|
||||||
version = "1.5.0"
|
version = "1.6.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
@ -31,6 +31,7 @@ poise = "0.6.1"
|
|||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.19"
|
||||||
log = "0.4.26"
|
log = "0.4.26"
|
||||||
cta-api = { version = "0.2.0", registry = "ahines"}
|
cta-api = { version = "0.2.0", registry = "ahines"}
|
||||||
|
thiserror = "2.0.12"
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
version = "1.35.1"
|
version = "1.35.1"
|
||||||
|
|||||||
@ -44,6 +44,7 @@ pub struct BotConfig {
|
|||||||
pub effect_role_duration: i64,
|
pub effect_role_duration: i64,
|
||||||
pub raas_server: String,
|
pub raas_server: String,
|
||||||
pub cta_key: String,
|
pub cta_key: String,
|
||||||
|
pub omdb_key: String,
|
||||||
pub picox: PicOxConfig,
|
pub picox: PicOxConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ use poise::serenity_prelude::{
|
|||||||
FormattedTimestampStyle, MessageBuilder, Timestamp, UserId,
|
FormattedTimestampStyle, MessageBuilder, Timestamp, UserId,
|
||||||
};
|
};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use crate::models::movie::Movie;
|
||||||
|
|
||||||
pub async fn is_admin(ctx: Context<'_>) -> Result<bool, Error> {
|
pub async fn is_admin(ctx: Context<'_>) -> Result<bool, Error> {
|
||||||
Ok(ctx.data().cfg.admins.contains(&ctx.author().id))
|
Ok(ctx.data().cfg.admins.contains(&ctx.author().id))
|
||||||
@ -337,3 +338,12 @@ pub async fn list_social_credit_phrases(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove a movie from the library
|
||||||
|
#[poise::command(prefix_command, category = "Admin", check = "is_admin")]
|
||||||
|
pub async fn remove_movie(ctx: Context<'_>, #[description = "Name of the movie"] movie: String,) -> Result<(), Error> {
|
||||||
|
Movie::remove_movie(&ctx.data().db, &movie)?;
|
||||||
|
|
||||||
|
ctx.reply(format!("Removed *{}* from the library", movie)).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ mod fren_coin;
|
|||||||
mod joke;
|
mod joke;
|
||||||
mod little_fren;
|
mod little_fren;
|
||||||
mod motivate;
|
mod motivate;
|
||||||
|
mod movie;
|
||||||
mod role;
|
mod role;
|
||||||
pub(crate) mod shop;
|
pub(crate) mod shop;
|
||||||
mod transit;
|
mod transit;
|
||||||
@ -304,6 +305,7 @@ pub async fn run_bot(global_data: GlobalData) {
|
|||||||
admin::add_social_credit_phrase(),
|
admin::add_social_credit_phrase(),
|
||||||
admin::remove_social_credit_phrase(),
|
admin::remove_social_credit_phrase(),
|
||||||
admin::list_social_credit_phrases(),
|
admin::list_social_credit_phrases(),
|
||||||
|
admin::remove_movie(),
|
||||||
album::add_image(),
|
album::add_image(),
|
||||||
album::list_albums(),
|
album::list_albums(),
|
||||||
birthday::add_birthday(),
|
birthday::add_birthday(),
|
||||||
@ -336,6 +338,10 @@ pub async fn run_bot(global_data: GlobalData) {
|
|||||||
motivate::motivation(),
|
motivate::motivation(),
|
||||||
motivate::motivation_add_album(),
|
motivate::motivation_add_album(),
|
||||||
motivate::green_screen(),
|
motivate::green_screen(),
|
||||||
|
movie::add_movie(),
|
||||||
|
movie::list_movies(),
|
||||||
|
movie::rate_movie(),
|
||||||
|
movie::movie(),
|
||||||
role::list_roles(),
|
role::list_roles(),
|
||||||
role::join_role(),
|
role::join_role(),
|
||||||
role::leave_role(),
|
role::leave_role(),
|
||||||
|
|||||||
137
src/discord/movie.rs
Normal file
137
src/discord/movie.rs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
use crate::discord::Context;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::models::movie::{LOWER_RATING, Movie, UPPER_RATING};
|
||||||
|
use poise::CreateReply;
|
||||||
|
use poise::serenity_prelude::{Attachment, Color, CreateEmbed, Mentionable, MessageBuilder};
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Add a movie to the library
|
||||||
|
#[poise::command(prefix_command, category = "Movies")]
|
||||||
|
pub async fn add_movie(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Name of the movie"] movie: String,
|
||||||
|
#[description = "What to give the rating out of. e.g ⭐"] rating: String,
|
||||||
|
#[description = "Movie Poster"] movie_poser: Attachment,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let data = movie_poser.download().await?;
|
||||||
|
|
||||||
|
let poster = ctx
|
||||||
|
.data()
|
||||||
|
.picox
|
||||||
|
.add_image(
|
||||||
|
"MoviePosters",
|
||||||
|
vec![movie.clone()],
|
||||||
|
&movie_poser.filename,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let movie = Movie::add_movie(&ctx.data().db, movie, rating, poster.link)?;
|
||||||
|
|
||||||
|
ctx.reply(format!(
|
||||||
|
"`{}` has been added to the Fleecebuster Hall of Fame",
|
||||||
|
movie.name
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rate a movie in the library
|
||||||
|
///
|
||||||
|
/// Ratings are from -10.0 to 10.0.
|
||||||
|
/// * Movies rated -10.0 are terrible but super enjoyable
|
||||||
|
/// * Movies rated 0.0 are bad and boring, a true sin
|
||||||
|
/// * Movies rated 10.0 are really good
|
||||||
|
#[poise::command(prefix_command, category = "Movies")]
|
||||||
|
pub async fn rate_movie(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Name of the movie"] movie: String,
|
||||||
|
#[description = "Rating to give the movie from -10.0 to 10.0"] rating: f32,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if !(LOWER_RATING..=UPPER_RATING).contains(&rating) {
|
||||||
|
ctx.reply("Ratings must be from -10.0 to 10.0!").await?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
Movie::add_rating(&ctx.data().db, &movie, ctx.author().id, rating)?;
|
||||||
|
|
||||||
|
ctx.reply("Your rating has been added!").await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const OMDB_URL: &str = "https://www.omdbapi.com/";
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct MovieResp {
|
||||||
|
#[serde(rename = "Title")]
|
||||||
|
title: String,
|
||||||
|
#[serde(rename = "Year")]
|
||||||
|
year: String,
|
||||||
|
#[serde(rename = "Plot")]
|
||||||
|
plot: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get information on a movie
|
||||||
|
#[poise::command(prefix_command, category = "Movies")]
|
||||||
|
pub async fn movie(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Name of the movie"] #[rest] movie: String,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let movie = Movie::get_movie(&ctx.data().db, &movie)?;
|
||||||
|
|
||||||
|
let client = Client::new();
|
||||||
|
|
||||||
|
let metadata: MovieResp = client
|
||||||
|
.get(OMDB_URL)
|
||||||
|
.query(&[("t", movie.name.clone()), ("apikey", ctx.data().cfg.omdb_key.clone())])
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut ratings = MessageBuilder::new();
|
||||||
|
|
||||||
|
for (user, rating) in &movie.ratings {
|
||||||
|
ratings.push_line(format!("* {}: {} {}", user.mention(), rating, &movie.rating_object));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.send(
|
||||||
|
CreateReply::default().embed(
|
||||||
|
CreateEmbed::new()
|
||||||
|
.title(format!("{} ({})", movie.name, metadata.year))
|
||||||
|
.description(metadata.plot)
|
||||||
|
.image(movie.poster.clone())
|
||||||
|
.field("Flock Rating", format!("{} {}", movie.calculate_rating(), movie.rating_object), true)
|
||||||
|
.field("Ratings:", ratings.build(), false)
|
||||||
|
.color(Color::from_rgb(0x11, 0x40, 0xaa))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all the movies in the library
|
||||||
|
#[poise::command(prefix_command, category = "Movies")]
|
||||||
|
pub async fn list_movies(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let movies = Movie::get_movies(&ctx.data().db)?;
|
||||||
|
|
||||||
|
let mut msg_builder = MessageBuilder::new();
|
||||||
|
|
||||||
|
msg_builder.push_line("# Fleecebuster Movies:");
|
||||||
|
|
||||||
|
for movie in &movies {
|
||||||
|
msg_builder.push_line(format!("* *{}* {} {}", movie.name, movie.calculate_rating(), movie.rating_object));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.reply(msg_builder.build()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -4,6 +4,7 @@ pub mod insult_compliment;
|
|||||||
pub mod lil_fren;
|
pub mod lil_fren;
|
||||||
pub mod managed_roles;
|
pub mod managed_roles;
|
||||||
pub mod motivation;
|
pub mod motivation;
|
||||||
|
pub mod movie;
|
||||||
pub mod race;
|
pub mod race;
|
||||||
pub mod random;
|
pub mod random;
|
||||||
pub mod social_credit;
|
pub mod social_credit;
|
||||||
|
|||||||
94
src/models/movie.rs
Normal file
94
src/models/movie.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
use crate::error::Error;
|
||||||
|
use j_db::database::Database;
|
||||||
|
use j_db::error::JDbError;
|
||||||
|
use poise::serenity_prelude::UserId;
|
||||||
|
use reqwest::Url;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub const LOWER_RATING: f32 = -10.0;
|
||||||
|
pub const UPPER_RATING: f32 = 10.0;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Movie {
|
||||||
|
id: Option<u64>,
|
||||||
|
pub name: String,
|
||||||
|
pub rating_object: String,
|
||||||
|
pub poster: Url,
|
||||||
|
pub ratings: HashMap<UserId, f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl j_db::model::JdbModel for Movie {
|
||||||
|
fn id(&self) -> Option<u64> {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_id(&mut self, id: u64) {
|
||||||
|
self.id = Some(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tree() -> String {
|
||||||
|
"Movie".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_unique(&self, other: &Self) -> bool {
|
||||||
|
!self.name.eq_ignore_ascii_case(&other.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Movie {
|
||||||
|
pub fn add_movie(
|
||||||
|
db: &Database,
|
||||||
|
name: String,
|
||||||
|
rating_object: String,
|
||||||
|
poster: Url,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
Ok(db.insert(Self {
|
||||||
|
id: None,
|
||||||
|
name,
|
||||||
|
rating_object,
|
||||||
|
poster,
|
||||||
|
ratings: Default::default(),
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_movie(db: &Database, name: &str) -> Result<Self, Error> {
|
||||||
|
Ok(db
|
||||||
|
.filter(|_, movie: &Movie| movie.name.eq_ignore_ascii_case(name))?
|
||||||
|
.next()
|
||||||
|
.ok_or(JDbError::NotFound)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_movies(db: &Database) -> Result<Vec<Self>, Error> {
|
||||||
|
Ok(db
|
||||||
|
.filter(|_, _movie: &Movie| true)?
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_movie(db: &Database, name: &str) -> Result<Self, Error> {
|
||||||
|
let movie = Self::get_movie(db, name)?;
|
||||||
|
|
||||||
|
db.remove::<Self>(movie.id.unwrap())?;
|
||||||
|
|
||||||
|
Ok(movie)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_rating(db: &Database, name: &str, user: UserId, rating: f32) -> Result<(), Error> {
|
||||||
|
let mut movie = Self::get_movie(db, name)?;
|
||||||
|
|
||||||
|
movie.ratings.insert(user, rating);
|
||||||
|
|
||||||
|
db.insert(movie)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_rating(&self) -> f32 {
|
||||||
|
if self.ratings.is_empty() {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.ratings.values().copied().sum::<f32>() / self.ratings.len() as f32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user