diff options
| -rw-r--r-- | Cargo.lock | 113 | ||||
| -rw-r--r-- | Cargo.toml | 7 | ||||
| -rw-r--r-- | src/api/guzzle.rs | 30 | ||||
| -rw-r--r-- | src/api/mod.rs | 6 | ||||
| -rw-r--r-- | src/api/shiggy.rs | 30 | ||||
| -rw-r--r-- | src/colors.rs | 2 | ||||
| -rw-r--r-- | src/commands/ask.rs | 23 | ||||
| -rw-r--r-- | src/commands/bing.rs | 6 | ||||
| -rw-r--r-- | src/commands/convert.rs | 29 | ||||
| -rw-r--r-- | src/commands/copypasta.rs | 10 | ||||
| -rw-r--r-- | src/commands/mod.rs | 8 | ||||
| -rw-r--r-- | src/commands/random.rs | 42 | ||||
| -rw-r--r-- | src/commands/teawiespam.rs | 8 | ||||
| -rw-r--r-- | src/commands/version.rs | 6 | ||||
| -rw-r--r-- | src/handlers/error.rs | 42 | ||||
| -rw-r--r-- | src/handlers/event/message.rs (renamed from src/handler/message.rs) | 36 | ||||
| -rw-r--r-- | src/handlers/event/mod.rs (renamed from src/handler/mod.rs) | 16 | ||||
| -rw-r--r-- | src/handlers/event/pinboard.rs (renamed from src/handler/pinboard.rs) | 0 | ||||
| -rw-r--r-- | src/handlers/event/reactboard.rs (renamed from src/handler/reactboard.rs) | 38 | ||||
| -rw-r--r-- | src/handlers/mod.rs | 5 | ||||
| -rw-r--r-- | src/main.rs | 73 | ||||
| -rw-r--r-- | src/utils.rs | 60 |
22 files changed, 355 insertions, 235 deletions
@@ -222,6 +222,33 @@ dependencies = [ ] [[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] name = "core-foundation" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -384,6 +411,16 @@ dependencies = [ ] [[package]] +name = "eyre" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] name = "flate2" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -706,6 +743,12 @@ dependencies = [ ] [[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -748,6 +791,12 @@ dependencies = [ ] [[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] name = "libc" version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -861,6 +910,12 @@ dependencies = [ ] [[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1378,6 +1433,24 @@ dependencies = [ ] [[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1490,6 +1563,7 @@ name = "teawiebot" version = "1.0.0" dependencies = [ "bottomify", + "color-eyre", "dotenvy", "env_logger", "include_dir", @@ -1542,6 +1616,16 @@ dependencies = [ ] [[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] name = "time" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1597,6 +1681,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", + "signal-hook-registry", "socket2 0.5.5", "tokio-macros", "windows-sys", @@ -1684,6 +1769,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", ] [[package]] @@ -1792,6 +1899,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -10,6 +10,7 @@ readme = "README.md" [dependencies] bottomify = "1.2.0" +color-eyre = "0.6.2" dotenvy = "0.15.7" env_logger = "0.10.0" include_dir = "0.7.3" @@ -22,5 +23,9 @@ reqwest = { version = "0.11.22", default-features = false, features = [ "json", ] } serde = "1.0.193" -tokio = { version = "1.33.0", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.33.0", features = [ + "macros", + "rt-multi-thread", + "signal", +] } url = { version = "2.5.0", features = ["serde"] } diff --git a/src/api/guzzle.rs b/src/api/guzzle.rs index 17d2c0c..83c159e 100644 --- a/src/api/guzzle.rs +++ b/src/api/guzzle.rs @@ -1,6 +1,6 @@ use crate::api::REQWEST_CLIENT; -use crate::Error; +use color_eyre::eyre::{eyre, Result}; use log::*; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; @@ -11,32 +11,20 @@ struct GuzzleResponse { } const GUZZLE: &str = "https://api.mydadleft.me"; +const RANDOM_TEAWIE: &str = "/random_teawie"; -pub async fn get_random_teawie() -> Result<String, Error> { - let endpoint = "/get_random_teawie"; - +pub async fn get_random_teawie() -> Result<String> { let req = REQWEST_CLIENT - .get(format!("{GUZZLE}{endpoint}")) - .build() - .unwrap(); + .get(format!("{GUZZLE}{RANDOM_TEAWIE}")) + .build()?; info!("making request to {}", req.url()); - let resp = REQWEST_CLIENT.execute(req).await.unwrap(); + let resp = REQWEST_CLIENT.execute(req).await?; let status = resp.status(); if let StatusCode::OK = status { - match resp.json::<GuzzleResponse>().await { - Ok(data) => Ok(data.url), - Err(why) => { - if let Some(url) = why.url() { - error!("error parsing json from {}! {}", url, why) - } else { - error!("couldn't even get the url! {}", why); - } - - Err(Box::new(why)) - } - } + let data = resp.json::<GuzzleResponse>().await?; + Ok(data.url) } else { error!( "couldn't fetch random teawie from {}! {}", @@ -44,6 +32,6 @@ pub async fn get_random_teawie() -> Result<String, Error> { status ); - Err(status.to_string().into()) + Err(eyre!("failed to get random teawie with {status}")) } } diff --git a/src/api/mod.rs b/src/api/mod.rs index a1e0e97..2ce664e 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -3,11 +3,13 @@ use once_cell::sync::Lazy; pub mod guzzle; pub mod shiggy; -pub const USER_AGENT: &str = "teawieBot/0.1.0"; +pub const USER_AGENT: &str = "teawieBot/"; pub static REQWEST_CLIENT: Lazy<reqwest::Client> = Lazy::new(|| { + let version = option_env!("CARGO_PKG_VERSION").unwrap_or("development"); + reqwest::Client::builder() - .user_agent(USER_AGENT) + .user_agent(format!("{USER_AGENT}/{version}")) .build() .unwrap_or_default() }); diff --git a/src/api/shiggy.rs b/src/api/shiggy.rs index 8dbadef..7a582ee 100644 --- a/src/api/shiggy.rs +++ b/src/api/shiggy.rs @@ -1,42 +1,30 @@ use crate::api::REQWEST_CLIENT; -use crate::Error; +use color_eyre::eyre::{eyre, Result}; use log::*; use reqwest::StatusCode; use serde::Deserialize; const SHIGGY: &str = "https://safebooru.donmai.us"; +const RANDOM_SHIGGY: &str = "/posts/random.json?tags=kemomimi-chan_(naga_u)+naga_u&only=file_url"; #[derive(Deserialize)] struct SafebooruResponse { file_url: String, } -pub async fn get_random_shiggy() -> Result<String, Error> { - let endpoint = "/posts/random.json?tags=kemomimi-chan_(naga_u)+naga_u&only=file_url"; - +pub async fn get_random_shiggy() -> Result<String> { let req = REQWEST_CLIENT - .get(format!("{SHIGGY}{endpoint}")) - .build() - .unwrap(); + .get(format!("{SHIGGY}{RANDOM_SHIGGY}")) + .build()?; info!("making request to {}", req.url()); - let resp = REQWEST_CLIENT.execute(req).await.unwrap(); + let resp = REQWEST_CLIENT.execute(req).await?; let status = resp.status(); if let StatusCode::OK = status { - match resp.json::<SafebooruResponse>().await { - Ok(data) => Ok(data.file_url), - Err(why) => { - if let Some(url) = why.url() { - error!("failed to make a request to {}! {}", url, why) - } else { - error!("couldn't even figure out the url! {}", why) - }; - - Err(Box::new(why)) - } - } + let data = resp.json::<SafebooruResponse>().await?; + Ok(data.file_url) } else { error!( "couldn't fetch random teawie from {}! {}", @@ -44,6 +32,6 @@ pub async fn get_random_shiggy() -> Result<String, Error> { status ); - Err(status.to_string().into()) + Err(eyre!("failed to get random teawie with {status}")) } } diff --git a/src/colors.rs b/src/colors.rs index ebd231e..5291933 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -2,12 +2,14 @@ use poise::serenity_prelude::Colour; pub enum Colors { Blue, + Orange, } impl From<Colors> for Colour { fn from(val: Colors) -> Self { match val { Colors::Blue => Colour::from((136, 199, 253)), + Colors::Orange => Colour::from((255, 179, 74)), } } } diff --git a/src/commands/ask.rs b/src/commands/ask.rs index 7cc82a1..3589484 100644 --- a/src/commands/ask.rs +++ b/src/commands/ask.rs @@ -1,6 +1,6 @@ -use crate::consts; -use crate::utils; -use crate::{Context, Error}; +use crate::{consts, utils, Context}; + +use color_eyre::eyre::{Context as _, Result}; /// ask teawie a question! #[poise::command(prefix_command, slash_command)] @@ -9,15 +9,10 @@ pub async fn ask( #[description = "the question you want to ask teawie"] #[rename = "question"] _question: String, -) -> Result<(), Error> { - match utils::random_choice(consts::RESPONSES) { - Ok(resp) => { - ctx.say(resp).await?; - Ok(()) - } - Err(why) => { - ctx.say("idk").await?; - Err(why) - } - } +) -> Result<()> { + let resp = utils::random_choice(consts::RESPONSES) + .wrap_err("couldn't choose from random responses!")?; + + ctx.say(resp).await?; + Ok(()) } diff --git a/src/commands/bing.rs b/src/commands/bing.rs index ed91bb3..b80ebca 100644 --- a/src/commands/bing.rs +++ b/src/commands/bing.rs @@ -1,8 +1,10 @@ -use crate::{Context, Error}; +use crate::Context; + +use color_eyre::eyre::Result; /// make sure the wie is alive #[poise::command(prefix_command)] -pub async fn bing(ctx: Context<'_>) -> Result<(), Error> { +pub async fn bing(ctx: Context<'_>) -> Result<()> { ctx.say("bong!").await?; Ok(()) } diff --git a/src/commands/convert.rs b/src/commands/convert.rs index 1f39ae4..cbbf8dc 100644 --- a/src/commands/convert.rs +++ b/src/commands/convert.rs @@ -1,11 +1,13 @@ -use crate::{Context, Error}; +use crate::Context; + use bottomify::bottom; +use color_eyre::eyre::Result; #[poise::command( slash_command, subcommands("to_fahrenheit", "to_celsius", "to_bottom", "from_bottom") )] -pub async fn convert(_ctx: Context<'_>) -> Result<(), Error> { +pub async fn convert(_ctx: Context<'_>) -> Result<()> { Ok(()) } @@ -14,7 +16,7 @@ pub async fn convert(_ctx: Context<'_>) -> Result<(), Error> { pub async fn to_celsius( ctx: Context<'_>, #[description = "what teawie will convert"] degrees_fahrenheit: f32, -) -> Result<(), Error> { +) -> Result<()> { let temp = (degrees_fahrenheit - 32.0) * (5.0 / 9.0); ctx.say(temp.to_string()).await?; Ok(()) @@ -25,7 +27,7 @@ pub async fn to_celsius( pub async fn to_fahrenheit( ctx: Context<'_>, #[description = "what teawie will convert"] degrees_celsius: f32, -) -> Result<(), Error> { +) -> Result<()> { let temp = (degrees_celsius * (9.0 / 5.0)) + 32.0; ctx.say(temp.to_string()).await?; Ok(()) @@ -36,7 +38,7 @@ pub async fn to_fahrenheit( pub async fn to_bottom( ctx: Context<'_>, #[description = "what teawie will translate into bottom"] message: String, -) -> Result<(), Error> { +) -> Result<()> { let encoded = bottom::encode_string(&message); ctx.say(encoded).await?; Ok(()) @@ -47,17 +49,8 @@ pub async fn to_bottom( pub async fn from_bottom( ctx: Context<'_>, #[description = "what teawie will translate from bottom"] message: String, -) -> Result<(), Error> { - let d = bottom::decode_string(&message); - match d { - Ok(decoded) => { - ctx.say(decoded).await?; - Ok(()) - } - Err(why) => { - ctx.say("couldn't decode that for you, i'm sowwy!! :((".to_string()) - .await?; - Err(Box::new(why)) - } - } +) -> Result<()> { + let decoded = bottom::decode_string(&message)?; + ctx.say(decoded).await?; + Ok(()) } diff --git a/src/commands/copypasta.rs b/src/commands/copypasta.rs index 14a6673..313cd13 100644 --- a/src/commands/copypasta.rs +++ b/src/commands/copypasta.rs @@ -1,7 +1,8 @@ -use crate::{utils, Context, Error}; +use crate::{utils, Context}; use std::collections::HashMap; +use color_eyre::eyre::{eyre, Result}; use include_dir::{include_dir, Dir}; use log::*; @@ -58,8 +59,11 @@ fn get_copypasta(name: Copypastas) -> String { pub async fn copypasta( ctx: Context<'_>, #[description = "the copypasta you want to send"] copypasta: Copypastas, -) -> Result<(), Error> { - let gid = ctx.guild_id().unwrap_or_default(); +) -> Result<()> { + let gid = ctx + .guild_id() + .ok_or_else(|| eyre!("couldnt get guild from message!"))?; + if !utils::is_guild_allowed(gid) { info!("not running copypasta command in {gid}"); return Ok(()); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index b6130ab..5edf0b7 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -6,10 +6,12 @@ pub mod random; pub mod teawiespam; pub mod version; -use crate::{Data, Error}; +use crate::Data; + +use color_eyre::eyre::Report; use poise::Command; -pub fn to_global_commands() -> Vec<Command<Data, Error>> { +pub fn to_global_commands() -> Vec<Command<Data, Report>> { vec![ ask::ask(), bing::bing(), @@ -21,6 +23,6 @@ pub fn to_global_commands() -> Vec<Command<Data, Error>> { ] } -pub fn to_guild_commands() -> Vec<Command<Data, Error>> { +pub fn to_guild_commands() -> Vec<Command<Data, Report>> { vec![copypasta::copypasta(), teawiespam::teawiespam()] } diff --git a/src/commands/random.rs b/src/commands/random.rs index bc34928..9595d09 100644 --- a/src/commands/random.rs +++ b/src/commands/random.rs @@ -1,44 +1,30 @@ -use crate::{api, consts, utils, Context, Error}; +use crate::{api, consts, utils, Context}; + +use color_eyre::eyre::Result; #[poise::command(slash_command, subcommands("lore", "teawie", "shiggy"))] -pub async fn random(_ctx: Context<'_>) -> Result<(), Error> { +pub async fn random(_ctx: Context<'_>) -> Result<()> { Ok(()) } /// get a random piece of teawie lore! #[poise::command(prefix_command, slash_command)] -pub async fn lore(ctx: Context<'_>) -> Result<(), Error> { - match utils::random_choice(consts::LORE) { - Ok(resp) => { - ctx.say(resp).await?; - Ok(()) - } - Err(why) => { - ctx.say("i can't think of any right now :(").await?; - Err(why) - } - } +pub async fn lore(ctx: Context<'_>) -> Result<()> { + let resp = utils::random_choice(consts::LORE)?; + ctx.say(resp).await?; + Ok(()) } /// get a random teawie #[poise::command(prefix_command, slash_command)] -pub async fn teawie(ctx: Context<'_>) -> Result<(), Error> { - if let Ok(url) = api::guzzle::get_random_teawie().await { - utils::send_url_as_embed(ctx, url).await - } else { - ctx.say("i'm too lazy to send a selfie right now :(") - .await?; - Ok(()) - } +pub async fn teawie(ctx: Context<'_>) -> Result<()> { + let url = api::guzzle::get_random_teawie().await?; + utils::send_url_as_embed(ctx, url).await } /// get a random shiggy #[poise::command(prefix_command, slash_command)] -pub async fn shiggy(ctx: Context<'_>) -> Result<(), Error> { - if let Ok(url) = api::shiggy::get_random_shiggy().await { - utils::send_url_as_embed(ctx, url).await - } else { - ctx.say("i couldn't get a shiggy right now :(").await?; - Ok(()) - } +pub async fn shiggy(ctx: Context<'_>) -> Result<()> { + let url = api::shiggy::get_random_shiggy().await?; + utils::send_url_as_embed(ctx, url).await } diff --git a/src/commands/teawiespam.rs b/src/commands/teawiespam.rs index 4964e90..da01af9 100644 --- a/src/commands/teawiespam.rs +++ b/src/commands/teawiespam.rs @@ -1,13 +1,15 @@ use crate::utils; -use crate::{Context, Error}; +use crate::Context; + +use color_eyre::eyre::Result; use log::*; /// teawie will spam you. #[poise::command(slash_command, prefix_command)] -pub async fn teawiespam(ctx: Context<'_>) -> Result<(), Error> { +pub async fn teawiespam(ctx: Context<'_>) -> Result<()> { let gid = ctx.guild_id().unwrap_or_default(); if !utils::is_guild_allowed(gid) { - info!("not running copypasta command in {gid}"); + info!("not running teawiespam command in {gid}"); return Ok(()); } diff --git a/src/commands/version.rs b/src/commands/version.rs index e87ab4d..c5e97f9 100644 --- a/src/commands/version.rs +++ b/src/commands/version.rs @@ -1,9 +1,11 @@ use crate::colors::Colors; -use crate::{Context, Error}; +use crate::Context; + +use color_eyre::eyre::Result; /// get version info #[poise::command(slash_command)] -pub async fn version(ctx: Context<'_>) -> Result<(), Error> { +pub async fn version(ctx: Context<'_>) -> Result<()> { let sha = option_env!("GIT_SHA").unwrap_or("main"); let revision_url = format!( diff --git a/src/handlers/error.rs b/src/handlers/error.rs new file mode 100644 index 0000000..b4e1361 --- /dev/null +++ b/src/handlers/error.rs @@ -0,0 +1,42 @@ +use crate::colors::Colors; +use crate::Data; + +use color_eyre::eyre::Report; +use log::*; +use poise::serenity_prelude::Timestamp; +use poise::FrameworkError; + +pub async fn handle(error: poise::FrameworkError<'_, Data, Report>) { + match error { + FrameworkError::Setup { error, .. } => error!("error setting up client! {error:#?}"), + + FrameworkError::Command { error, ctx } => { + error!("error in command {}:\n{error:?}", ctx.command().name); + ctx.send(|c| { + c.embed(|e| { + e.title("Something went wrong!") + .description("oopsie") + .timestamp(Timestamp::now()) + .color(Colors::Orange) + }) + }) + .await + .ok(); + } + + FrameworkError::EventHandler { + error, + ctx: _, + event: _, + framework: _, + } => { + error!("error while handling event:\n{error:#?}"); + } + + error => { + if let Err(e) = poise::builtins::on_error(error).await { + error!("error while handling an error: {}", e); + } + } + } +} diff --git a/src/handler/message.rs b/src/handlers/event/message.rs index 37a49bf..a84ec59 100644 --- a/src/handler/message.rs +++ b/src/handlers/event/message.rs @@ -1,9 +1,23 @@ -use crate::{consts, utils, Data, Error}; +use crate::{consts, utils, Data}; + +use color_eyre::eyre::{Report, Result}; use log::*; -use poise::serenity_prelude as serenity; -use poise::{Event, FrameworkContext}; +use poise::serenity_prelude::{Context, Message}; +use poise::FrameworkContext; + +pub async fn handle( + ctx: &Context, + framework: FrameworkContext<'_, Data, Report>, + msg: &Message, +) -> Result<()> { + if should_echo(framework, msg) { + msg.reply(ctx, &msg.content).await?; + } + + Ok(()) +} -fn should_echo(framework: FrameworkContext<'_, Data, Error>, msg: &serenity::Message) -> bool { +fn should_echo(framework: FrameworkContext<'_, Data, Report>, msg: &Message) -> bool { let gid = msg.guild_id.unwrap_or_default(); if msg.author.id == framework.bot_id || !utils::is_guild_allowed(gid) { info!("not running copypasta command in {gid}"); @@ -19,17 +33,3 @@ fn should_echo(framework: FrameworkContext<'_, Data, Error>, msg: &serenity::Mes .to_ascii_lowercase() .contains("twitter's recommendation algorithm") } - -pub async fn handle( - ctx: &serenity::Context, - _event: &Event<'_>, - framework: FrameworkContext<'_, Data, Error>, - _data: &Data, - msg: &serenity::Message, -) -> Result<(), Error> { - if should_echo(framework, msg) { - msg.reply(ctx, &msg.content).await?; - } - - Ok(()) -} diff --git a/src/handler/mod.rs b/src/handlers/event/mod.rs index 3489b4a..09be62b 100644 --- a/src/handler/mod.rs +++ b/src/handlers/event/mod.rs @@ -1,25 +1,25 @@ -use crate::{Data, Error}; +use crate::Data; + +use color_eyre::eyre::{Report, Result}; use poise::serenity_prelude as serenity; -use poise::Event; +use poise::{Event, FrameworkContext}; mod message; -pub mod pinboard; +mod pinboard; mod reactboard; pub async fn handle( ctx: &serenity::Context, event: &Event<'_>, - framework: poise::FrameworkContext<'_, Data, Error>, + framework: FrameworkContext<'_, Data, Report>, data: &Data, -) -> Result<(), Error> { +) -> Result<()> { match event { Event::Ready { data_about_bot } => { log::info!("logged in as {}", data_about_bot.user.name) } - Event::Message { new_message } => { - message::handle(ctx, event, framework, data, new_message).await? - } + Event::Message { new_message } => message::handle(ctx, framework, new_message).await?, Event::ChannelPinsUpdate { pin } => { if let Some(settings) = &data.settings { diff --git a/src/handler/pinboard.rs b/src/handlers/event/pinboard.rs index 0c87a5b..0c87a5b 100644 --- a/src/handler/pinboard.rs +++ b/src/handlers/event/pinboard.rs diff --git a/src/handler/reactboard.rs b/src/handlers/event/reactboard.rs index 36f8361..3972931 100644 --- a/src/handler/reactboard.rs +++ b/src/handlers/event/reactboard.rs @@ -1,31 +1,29 @@ -use crate::Error; use crate::{settings::Settings, utils}; + +use color_eyre::eyre::{eyre, Context as _, Result}; use log::*; use poise::serenity_prelude::{Context, Message, MessageReaction, Reaction}; -pub async fn handle(ctx: &Context, reaction: &Reaction, settings: &Settings) -> Result<(), Error> { - let msg = match reaction.message(&ctx.http).await { - Ok(msg) => msg, - Err(why) => { - warn!("couldn't get message of reaction! {}", why); - return Err(Box::new(why)); - } - }; +pub async fn handle(ctx: &Context, reaction: &Reaction, settings: &Settings) -> Result<()> { + let msg = reaction + .message(&ctx.http) + .await + .wrap_err("couldn't get reaction from message!")?; - if let Some(matched) = msg + let matched = msg .clone() .reactions .into_iter() .find(|r| r.reaction_type == reaction.emoji) - { - send_to_reactboard(ctx, &matched, &msg, settings).await?; - } else { - warn!( - "couldn't find any matching reactions for {} in {}", - reaction.emoji.as_data(), - msg.id - ) - } + .ok_or_else(|| { + eyre!( + "couldn't find any matching reactions for {} in message {}!", + reaction.emoji.as_data(), + msg.id + ) + })?; + + send_to_reactboard(ctx, &matched, &msg, settings).await?; Ok(()) } @@ -35,7 +33,7 @@ async fn send_to_reactboard( reaction: &MessageReaction, msg: &Message, settings: &Settings, -) -> Result<(), Error> { +) -> Result<()> { if !settings.can_use_reaction(reaction) { info!("reaction {} can't be used!", reaction.reaction_type); return Ok(()); diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..2ae0539 --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,5 @@ +mod error; +mod event; + +pub use error::handle as handle_error; +pub use event::handle as handle_event; diff --git a/src/main.rs b/src/main.rs index a93e102..98ebe5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,21 @@ -use std::{error, time}; +use std::time::Duration; +use color_eyre::eyre::{eyre, Context as _, Report, Result}; use log::*; -use poise::serenity_prelude as serentiy; +use poise::{ + serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, +}; use settings::Settings; mod api; mod colors; mod commands; mod consts; -mod handler; +mod handlers; mod settings; mod utils; -type Error = Box<dyn error::Error + Send + Sync>; -type Context<'a> = poise::Context<'a, Data, Error>; +type Context<'a> = poise::Context<'a, Data, Report>; #[derive(Clone)] pub struct Data { @@ -34,54 +36,43 @@ impl Default for Data { } } -async fn on_error(error: poise::FrameworkError<'_, Data, Error>) { - match error { - poise::FrameworkError::Setup { error, .. } => panic!("failed to start bot: {error:?}"), - poise::FrameworkError::Command { error, ctx } => { - error!("error in command {}: {:?}", ctx.command().name, error); - } - error => { - if let Err(e) = poise::builtins::on_error(error).await { - error!("error while handling an error: {}", e); - } - } - } -} - #[tokio::main] -async fn main() { +async fn main() -> Result<()> { + color_eyre::install()?; env_logger::init(); dotenvy::dotenv().ok(); - let options = poise::FrameworkOptions { + let token = + std::env::var("TOKEN").wrap_err_with(|| eyre!("Couldn't find token in environment!"))?; + + let intents = + serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; + + let options = FrameworkOptions { commands: commands::to_global_commands(), + on_error: |error| Box::pin(handlers::handle_error(error)), + command_check: Some(|ctx| { + Box::pin(async move { Ok(ctx.author().id != ctx.framework().bot_id) }) + }), event_handler: |ctx, event, framework, data| { - Box::pin(handler::handle(ctx, event, framework, data)) + Box::pin(handlers::handle_event(ctx, event, framework, data)) }, - prefix_options: poise::PrefixFrameworkOptions { + prefix_options: PrefixFrameworkOptions { prefix: Some("!".into()), - edit_tracker: Some(poise::EditTracker::for_timespan(time::Duration::from_secs( - 3600, - ))), + edit_tracker: Some(EditTracker::for_timespan(Duration::from_secs(3600))), ..Default::default() }, - on_error: |error| Box::pin(on_error(error)), - command_check: Some(|ctx| { - Box::pin(async move { Ok(ctx.author().id != ctx.framework().bot_id) }) - }), ..Default::default() }; - let framework = poise::Framework::builder() + let framework = Framework::builder() + .token(token) + .intents(intents) .options(options) - .token(std::env::var("TOKEN").expect("couldn't find token in environment.")) - .intents( - serentiy::GatewayIntents::non_privileged() | serentiy::GatewayIntents::MESSAGE_CONTENT, - ) .setup(|ctx, _ready, framework| { Box::pin(async move { poise::builtins::register_globally(ctx, &framework.options().commands).await?; - info!("registered global commands!"); + info!("Registered global commands!"); poise::builtins::register_in_guild( ctx, @@ -89,11 +80,17 @@ async fn main() { consts::TEAWIE_GUILD, ) .await?; - info!("registered guild commands!"); + info!("Registered guild commands to {}", consts::TEAWIE_GUILD); Ok(Data::new()) }) }); - framework.run().await.unwrap() + tokio::select! { + result = framework.run() => { result.map_err(Report::from) }, + _ = tokio::signal::ctrl_c() => { + info!("Interrupted! Exiting..."); + std::process::exit(130); + } + } } diff --git a/src/utils.rs b/src/utils.rs index af079ff..9a1d09c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,6 @@ -use crate::{colors, consts, Context, Error}; +use crate::{colors, consts, Context}; -use log::*; +use color_eyre::eyre::{eyre, Result}; use once_cell::sync::Lazy; use poise::serenity_prelude as serenity; use rand::seq::SliceRandom; @@ -21,13 +21,13 @@ pub fn parse_snowflakes_from_env<T, F: Fn(u64) -> T>(key: &str, f: F) -> Option< /* * chooses a random element from an array */ -pub fn random_choice<const N: usize>(arr: [&str; N]) -> Result<String, Error> { +pub fn random_choice<const N: usize>(arr: [&str; N]) -> Result<String> { let mut rng = rand::thread_rng(); - if let Some(resp) = arr.choose(&mut rng) { - Ok((*resp).to_string()) - } else { - Err(Into::into("couldn't choose from arr!")) - } + let resp = arr + .choose(&mut rng) + .ok_or_else(|| eyre!("couldn't choose from array!"))?; + + Ok((*resp).to_string()) } // waiting for `round_char_boundary` to stabilize @@ -54,31 +54,25 @@ pub fn is_guild_allowed(gid: GuildId) -> bool { ALLOWED_GUILDS.contains(&gid) } -pub async fn send_url_as_embed(ctx: Context<'_>, url: String) -> Result<(), Error> { - match Url::parse(&url) { - Ok(parsed) => { - let title = parsed - .path_segments() - .unwrap() - .last() - .unwrap_or("image") - .replace("%20", " "); - - ctx.send(|c| { - c.embed(|e| { - e.title(title) - .image(&url) - .url(url) - .color(colors::Colors::Blue) - }) - }) - .await?; - } - Err(why) => { - error!("failed to parse url {}! {}", url, why); - ctx.say("i can't get that for you right now :(").await?; - } - } +pub async fn send_url_as_embed(ctx: Context<'_>, url: String) -> Result<()> { + let parsed = Url::parse(&url)?; + + let title = parsed + .path_segments() + .unwrap() + .last() + .unwrap_or("image") + .replace("%20", " "); + + ctx.send(|c| { + c.embed(|e| { + e.title(title) + .image(&url) + .url(url) + .color(colors::Colors::Blue) + }) + }) + .await?; Ok(()) } |
