Skip to content

Commit

Permalink
commands(tmdb): add movie command & merge tmdb models.
Browse files Browse the repository at this point in the history
  • Loading branch information
evieluvsrainbows committed Jul 19, 2024
1 parent 0501f8f commit b4d32ea
Show file tree
Hide file tree
Showing 11 changed files with 589 additions and 191 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rustfmt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
unstable_features = true

# Width formatting options
max_width = 188
max_width = 190

# General formatting options
trailing_comma = "Never"
8 changes: 4 additions & 4 deletions src/commands/fun/xkcd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ struct XkcdComic {

/// Retrieves the latest or a specific comic from xkcd.
#[command(slash_command)]
pub async fn xkcd(ctx: Context<'_>, #[description = "The specific comic no. to retrieve."] number: Option<u16>) -> Result<(), Error> {
pub async fn xkcd(context: Context<'_>, #[description = "The specific comic no. to retrieve."] number: Option<u16>) -> Result<(), Error> {
let comic = match number {
None => "https://xkcd.com/info.0.json",
Some(number) => &format!("https://xkcd.com/{number}/info.0.json")
};

let client = &ctx.data().reqwest_container;
let client = &context.data().reqwest_container;
let request = client.get(comic).send().await?;
if request.status() == StatusCode::NOT_FOUND {
ctx.reply("You did not provide a valid comic id.").await?;
context.reply("You did not provide a valid comic id.").await?;
return Ok(());
}

Expand All @@ -40,7 +40,7 @@ pub async fn xkcd(ctx: Context<'_>, #[description = "The specific comic no. to r
.footer(CreateEmbedFooter::new(format!("xkcd comic no. {num}")));

let links = CreateActionRow::Buttons(vec![CreateButton::new_link(page).label("View on xkcd"), CreateButton::new_link(wiki).label("View wiki")]);
ctx.send(poise::CreateReply::default().embed(embed).components(vec![links])).await?;
context.send(poise::CreateReply::default().embed(embed).components(vec![links])).await?;

Ok(())
}
136 changes: 128 additions & 8 deletions src/commands/search/tmdb/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
use crate::{Context, Error};
use crate::{
models::tmdb::Movie,
utils::{format_int, locale},
Context, Error
};
use chrono::NaiveDate;
use humantime::format_duration;
use itertools::Itertools;
use poise::CreateReply;
use serde::Deserialize;
use serenity::all::{CreateActionRow, CreateButton, CreateEmbed};
use serenity::all::{CreateActionRow, CreateButton, CreateEmbed, CreateEmbedFooter};
use std::time::Duration;

#[derive(Deserialize, Debug)]
pub struct SearchResponse {
Expand Down Expand Up @@ -30,22 +37,22 @@ pub struct SimplifiedMovie {
pub title: String // The title of the movie.
}

#[poise::command(slash_command, subcommands("collection"))]
pub async fn tmdb(_ctx: Context<'_>) -> Result<(), Error> {
#[poise::command(slash_command, subcommands("collection", "movie"))]
pub async fn tmdb(_context: Context<'_>) -> Result<(), Error> {
Ok(())
}

/// Retrieves information about a collection on TMDb.
#[poise::command(slash_command)]
pub async fn collection(ctx: Context<'_>, #[description = "The name of the collection."] name: String) -> Result<(), Error> {
let data = &ctx.data();
pub async fn collection(context: Context<'_>, #[description = "The name of the collection."] name: String) -> Result<(), Error> {
let data = &context.data();
let client = &data.reqwest_container;
let api_key = &data.config.api.entertainment.tmdb;
let search_response = client.get("https://api.themoviedb.org/3/search/collection").query(&[("api_key", api_key), ("query", &name)]);
let search_result: SearchResponse = search_response.send().await?.json().await?;
let search_results = search_result.results;
if search_results.is_empty() {
ctx.reply(format!("Nothing found for `{name}`. Please try another name.")).await?;
context.reply(format!("Nothing found for `{name}`. Please try another name.")).await?;
return Ok(());
}

Expand Down Expand Up @@ -76,7 +83,120 @@ pub async fn collection(ctx: Context<'_>, #[description = "The name of the colle
}).collect())).collect();

let embed = CreateEmbed::new().title(name).url(url).thumbnail(poster).color(0x0001_d277).description(overview).fields(fields);
ctx.send(CreateReply::default().embed(embed).components(rows)).await?;
context.send(CreateReply::default().embed(embed).components(rows)).await?;

Ok(())
}

/// Retrieves detailed information about a film from TMDb.
#[poise::command(slash_command)]
pub async fn movie(context: Context<'_>, #[description = "The film name."] name: String, #[description = "The film release year."] year: Option<u16>) -> Result<(), Error> {
let data = &context.data();
let api_key = &data.config.api.entertainment.tmdb;
let client = &data.reqwest_container;
let endpoint = "https://api.themoviedb.org/3/search/movie";
let response = match year {
Some(year) => client.get(endpoint).query(&[("api_key", api_key), ("query", &name), ("year", &year.to_string())]),
None => client.get(endpoint).query(&[("api_key", api_key), ("query", &name)])
};

let result: SearchResponse = response.send().await?.json().await?;
let results = result.results;
if results.is_empty() {
context.say(format!("No results found for `{name}`. Please try looking for another movie.")).await?;
}

let id = results.first().unwrap().id;
let endpoint = format!("https://api.themoviedb.org/3/movie/{id}");
let response = client.get(&endpoint).query(&[("api_key", &api_key)]).send().await?;
let result: Movie = response.json().await?;

let tagline = match result.tagline {
Some(tagline) => {
if tagline.is_empty() {
String::new()
} else {
format!("*{tagline}*")
}
}
None => String::new()
};

let overview = match result.overview {
Some(overview) => {
if !tagline.is_empty() {
format!("\n\n{overview}")
} else {
overview
}
}
None => String::new()
};

let studios = if result.production_companies.is_empty() {
"No Known Studios".to_string()
} else {
result.production_companies.iter().map(|c| &c.name).join("\n")
};

let collection = match result.belongs_to_collection {
Some(collection) => collection.name,
None => "N/A".to_string()
};

let homepage = match result.homepage {
Some(homepage) => {
if homepage.is_empty() {
"No Website".to_string()
} else {
format!("[Website]({homepage})")
}
}
None => "No Website".to_string()
};

let id = result.id.to_string();
let title = result.title.as_str();
let status = result.status;
let language = locale::get_language_name_from_iso(&result.original_language).to_string();
let release_date = result.release_date.unwrap().format("%B %e, %Y").to_string();
let budget = format_int(result.budget);
let revenue = format_int(result.revenue);
let imdb = format!("[IMDb](https://www.imdb.com/title/{})", result.imdb_id.unwrap());
let url = format!("https://www.themoviedb.org/movie/{id}");
let genres = result.genres.iter().map(|g| &g.name).join("\n");
let popularity = format!("{}%", result.popularity.round());
let poster_uri = result.poster_path.unwrap();
let poster = format!("https://image.tmdb.org/t/p/original/{}", &poster_uri.replace('/', ""));
let user_score = format!("{}/100", (result.vote_average * 10.0).round());
let user_score_count = result.vote_count;
let runtime = format_duration(Duration::from_secs(result.runtime.unwrap() * 60)).to_string();
let external_links = format!("{homepage} | {imdb}");

let embed = CreateEmbed::new()
.title(title)
.url(url)
.color(0x01b4e4)
.thumbnail(poster)
.description(format!("{tagline}{overview}"))
.fields(vec![
("Status", status, true),
("Film ID", id, true),
("Language", language, true),
("Runtime", runtime, true),
("Release Date", release_date, true),
("Collection", collection, true),
("Popularity", popularity, true),
("User Score", format!("{user_score} ({user_score_count} votes)"), true),
("Budget", format!("${budget}"), true),
("Box Office", format!("${revenue}"), true),
("Genres", genres, true),
("Studios", studios, true),
("External Links", external_links, false),
])
.footer(CreateEmbedFooter::new("Powered by the The Movie Database API."));

context.send(CreateReply::default().embed(embed)).await?;

Ok(())
}
12 changes: 6 additions & 6 deletions src/commands/utilities/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ use poise::command;

/// Shows the help menu.
#[command(slash_command, track_edits)]
pub async fn help(ctx: Context<'_>, #[description = "Specific command to show help about"] command: Option<String>) -> Result<(), Error> {
pub async fn help(context: Context<'_>, #[description = "Specific command to show help about"] command: Option<String>) -> Result<(), Error> {
let config = poise::builtins::HelpConfiguration { ..Default::default() };
poise::builtins::help(ctx, command.as_deref(), config).await?;
poise::builtins::help(context, command.as_deref(), config).await?;
Ok(())
}

/// Says hello to the user who initializes the command.
#[command(slash_command)]
pub async fn hello(ctx: Context<'_>) -> Result<(), Error> {
ctx.say(format!("Hello, **{}**!", ctx.author().name)).await?;
pub async fn hello(context: Context<'_>) -> Result<(), Error> {
context.say(format!("Hello, **{}**!", context.author().name)).await?;
Ok(())
}

/// Posts a message containing a link to the bot's source code on GitHub.
#[command(slash_command)]
pub async fn source(ctx: Context<'_>) -> Result<(), Error> {
ctx.reply("GitHub repository: <https://github.com/evelynharthbrooke/Taliyah>").await?;
pub async fn source(context: Context<'_>) -> Result<(), Error> {
context.reply("GitHub repository: <https://github.com/evelynharthbrooke/Taliyah>").await?;
Ok(())
}
6 changes: 4 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(dead_code)]

mod commands;
mod config;
mod constants;
Expand Down Expand Up @@ -61,11 +63,11 @@ async fn main() -> Result<(), Error> {
],
..Default::default()
})
.setup(move |ctx, _ready, framework| {
.setup(move |context, _ready, framework| {
Box::pin(async move {
// register all commands upon startup to avoid having to register them
// manually.
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
poise::builtins::register_globally(context, &framework.options().commands).await?;
Ok(Data {
config: read_config("config.toml"),
reqwest_container: Client::builder().user_agent(REQWEST_USER_AGENT).redirect(Policy::none()).build()?
Expand Down
Loading

0 comments on commit b4d32ea

Please sign in to comment.