refactor!: use poise 0.6.1

This commit is contained in:
seth 2024-01-27 22:29:56 -05:00
parent 203ba111cc
commit 7252ced3cb
No known key found for this signature in database
GPG key ID: D31BD0D494BBEE86
16 changed files with 700 additions and 512 deletions

642
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -10,30 +10,30 @@ build = "build.rs"
# 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
[build-dependencies] [build-dependencies]
gray_matter = "0.2.6" gray_matter = "0.2.6"
poise = "0.5.7" poise = "0.6.1"
serde = "1.0.193" serde = "1.0.196"
serde_json = "1.0.108" serde_json = "1.0.112"
[dependencies] [dependencies]
async-trait = "0.1.74" async-trait = "0.1.77"
color-eyre = "0.6.2" color-eyre = "0.6.2"
dotenvy = "0.15.7" dotenvy = "0.15.7"
env_logger = "0.10.0" env_logger = "0.11.1"
log = "0.4.20" log = "0.4.20"
poise = "0.5.7" poise = "0.6.1"
octocrab = "0.32.0" octocrab = "0.33.3"
once_cell = "1.18.0" once_cell = "1.19.0"
rand = "0.8.5" rand = "0.8.5"
redis = { version = "0.23.3", features = ["tokio-comp", "tokio-rustls-comp"] } redis = { version = "0.24.0", features = ["tokio-comp", "tokio-rustls-comp"] }
redis-macros = "0.2.1" redis-macros = "0.2.1"
regex = "1.10.2" regex = "1.10.3"
reqwest = { version = "0.11.22", default-features = false, features = [ reqwest = { version = "0.11.23", default-features = false, features = [
"rustls-tls", "rustls-tls",
"json", "json",
] } ] }
serde = "1.0.193" serde = "1.0.196"
serde_json = "1.0.108" serde_json = "1.0.112"
tokio = { version = "1.33.0", features = [ tokio = { version = "1.35.1", features = [
"macros", "macros",
"rt-multi-thread", "rt-multi-thread",
"signal", "signal",

View file

@ -1,11 +1,16 @@
use crate::{consts, Context}; use crate::{consts, Context};
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{eyre, Result};
use poise::serenity_prelude::CreateEmbed;
use poise::CreateReply;
/// Returns the number of members in the server /// Returns the number of members in the server
#[poise::command(slash_command, prefix_command)] #[poise::command(slash_command, prefix_command)]
pub async fn members(ctx: Context<'_>) -> Result<()> { pub async fn members(ctx: Context<'_>) -> Result<()> {
let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't fetch guild!"))?; let guild = ctx
.guild()
.ok_or_else(|| eyre!("Couldn't fetch guild!"))?
.to_owned();
let count = guild.member_count; let count = guild.member_count;
let online = if let Some(count) = guild.approximate_presence_count { let online = if let Some(count) = guild.approximate_presence_count {
@ -14,13 +19,12 @@ pub async fn members(ctx: Context<'_>) -> Result<()> {
"Undefined".to_string() "Undefined".to_string()
}; };
ctx.send(|m| { let embed = CreateEmbed::new()
m.embed(|e| { .title(format!("{count} total members!"))
e.title(format!("{count} total members!")) .description(format!("{online} online members"))
.description(format!("{online} online members")) .color(consts::COLORS["blue"]);
.color(consts::COLORS["blue"]) let reply = CreateReply::default().embed(embed);
})
}) ctx.send(reply).await?;
.await?;
Ok(()) Ok(())
} }

View file

@ -2,6 +2,8 @@ use crate::api::rory::get_rory;
use crate::Context; use crate::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use poise::serenity_prelude::{CreateEmbed, CreateEmbedFooter};
use poise::CreateReply;
/// Gets a Rory photo! /// Gets a Rory photo!
#[poise::command(slash_command, prefix_command)] #[poise::command(slash_command, prefix_command)]
@ -11,19 +13,22 @@ pub async fn rory(
) -> Result<()> { ) -> Result<()> {
let rory = get_rory(id).await?; let rory = get_rory(id).await?;
ctx.send(|m| { let embed = {
m.embed(|e| { let embed = CreateEmbed::new();
if let Some(error) = rory.error { if let Some(error) = rory.error {
e.title("Error!").description(error) embed.title("Error!").description(error)
} else { } else {
e.title("Rory :3") let footer = CreateEmbedFooter::new(format!("ID {}", rory.id));
.url(&rory.url) embed
.image(rory.url) .title("Rory :3")
.footer(|f| f.text(format!("ID {}", rory.id))) .url(&rory.url)
} .image(rory.url)
}) .footer(footer)
}) }
.await?; };
let reply = CreateReply::default().embed(embed);
ctx.send(reply).await?;
Ok(()) Ok(())
} }

View file

@ -1,6 +1,7 @@
use crate::Context; use crate::Context;
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{eyre, Result};
use poise::serenity_prelude::{CreateEmbed, CreateEmbedAuthor, CreateMessage};
/// Say something through the bot /// Say something through the bot
#[poise::command( #[poise::command(
@ -11,7 +12,10 @@ use color_eyre::eyre::{eyre, Result};
required_permissions = "MODERATE_MEMBERS" required_permissions = "MODERATE_MEMBERS"
)] )]
pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: String) -> Result<()> { pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: String) -> Result<()> {
let guild = ctx.guild().ok_or_else(|| eyre!("Couldn't get guild!"))?; let guild = ctx
.guild()
.ok_or_else(|| eyre!("Couldn't get guild!"))?
.to_owned();
let channel = ctx let channel = ctx
.guild_channel() .guild_channel()
.await .await
@ -28,23 +32,16 @@ pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: Str
.find(|c| c.0 == &channel_id) .find(|c| c.0 == &channel_id)
.ok_or_else(|| eyre!("Couldn't get log channel from guild!"))?; .ok_or_else(|| eyre!("Couldn't get log channel from guild!"))?;
log_channel let author = CreateEmbedAuthor::new(ctx.author().tag())
.1 .icon_url(ctx.author().avatar_url().unwrap_or("Undefined".to_string()));
.clone()
.guild() let embed = CreateEmbed::default()
.ok_or_else(|| eyre!("Couldn't cast channel we found from guild as GuildChannel?????"))? .title("Say command used!")
.send_message(ctx, |m| { .description(content)
m.embed(|e| { .author(author);
e.title("Say command used!")
.description(content) let message = CreateMessage::new().embed(embed);
.author(|a| { log_channel.1.send_message(ctx, message).await?;
a.name(ctx.author().tag()).icon_url(
ctx.author().avatar_url().unwrap_or("undefined".to_string()),
)
})
})
})
.await?;
} }
Ok(()) Ok(())

View file

@ -1,6 +1,8 @@
use crate::{consts::COLORS, Context}; use crate::{consts, Context};
use color_eyre::eyre::{Context as _, Result}; use color_eyre::eyre::{Context as _, Result};
use poise::serenity_prelude::CreateEmbed;
use poise::CreateReply;
/// Returns GitHub stargazer count /// Returns GitHub stargazer count
#[poise::command(slash_command, prefix_command)] #[poise::command(slash_command, prefix_command)]
@ -19,13 +21,12 @@ pub async fn stars(ctx: Context<'_>) -> Result<()> {
"undefined".to_string() "undefined".to_string()
}; };
ctx.send(|m| { let embed = CreateEmbed::new()
m.embed(|e| { .title(format!("{count} total stars!"))
e.title(format!("{count} total stars!")) .color(consts::COLORS["yellow"]);
.color(COLORS["yellow"]) let reply = CreateReply::default().embed(embed);
})
}) ctx.send(reply).await?;
.await?;
Ok(()) Ok(())
} }

View file

@ -5,7 +5,8 @@ use std::env;
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{eyre, Result};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use poise::serenity_prelude::{Color, User}; use poise::serenity_prelude::{Color, CreateEmbed, User};
use poise::CreateReply;
include!(concat!(env!("OUT_DIR"), "/generated.rs")); include!(concat!(env!("OUT_DIR"), "/generated.rs"));
static TAGS: Lazy<Vec<Tag>> = Lazy::new(|| serde_json::from_str(env!("TAGS")).unwrap()); static TAGS: Lazy<Vec<Tag>> = Lazy::new(|| serde_json::from_str(env!("TAGS")).unwrap());
@ -25,36 +26,40 @@ pub async fn tag(
let frontmatter = &tag.frontmatter; let frontmatter = &tag.frontmatter;
ctx.send(|m| { let embed = {
if let Some(user) = user { let mut e = CreateEmbed::new();
m.content(format!("<@{}>", user.id));
if let Some(color) = &frontmatter.color {
let color = *consts::COLORS
.get(color.as_str())
.unwrap_or(&Color::default());
e = e.color(color);
} }
m.embed(|e| { if let Some(image) = &frontmatter.image {
e.title(&frontmatter.title); e = e.image(image);
e.description(&tag.content); }
if let Some(color) = &frontmatter.color { if let Some(fields) = &frontmatter.fields {
let color = *consts::COLORS for field in fields {
.get(color.as_str()) e = e.field(&field.name, &field.value, field.inline);
.unwrap_or(&Color::default());
e.color(color);
} }
}
if let Some(image) = &frontmatter.image { e
e.image(image); };
}
if let Some(fields) = &frontmatter.fields { let reply = {
for field in fields { let mut r = CreateReply::default();
e.field(&field.name, &field.value, field.inline);
}
}
e if let Some(user) = user {
}) r = r.content(format!("<@{}>", user.id));
}) }
.await?;
r.embed(embed)
};
ctx.send(reply).await?;
Ok(()) Ok(())
} }

View file

@ -3,8 +3,8 @@ use crate::Data;
use color_eyre::eyre::Report; use color_eyre::eyre::Report;
use log::*; use log::*;
use poise::serenity_prelude::Timestamp; use poise::serenity_prelude::{CreateEmbed, Timestamp};
use poise::FrameworkError; use poise::{CreateReply, FrameworkError};
pub async fn handle(error: FrameworkError<'_, Data, Report>) { pub async fn handle(error: FrameworkError<'_, Data, Report>) {
match error { match error {
@ -12,23 +12,23 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) {
error, framework, .. error, framework, ..
} => { } => {
error!("Error setting up client! Bailing out"); error!("Error setting up client! Bailing out");
framework.shard_manager().lock().await.shutdown_all().await; framework.shard_manager().shutdown_all().await;
panic!("{error}") panic!("{error}")
} }
FrameworkError::Command { error, ctx } => { FrameworkError::Command { error, ctx, .. } => {
error!("Error in command {}:\n{error:?}", ctx.command().name); error!("Error in command {}:\n{error:?}", ctx.command().name);
ctx.send(|c| {
c.embed(|e| { let embed = CreateEmbed::new()
e.title("Something went wrong!") .title("Something went wrong!")
.description("oopsie") .description("oopsie")
.timestamp(Timestamp::now()) .timestamp(Timestamp::now())
.color(COLORS["red"]) .color(COLORS["red"]);
})
}) let reply = CreateReply::default().embed(embed);
.await
.ok(); ctx.send(reply).await.ok();
} }
FrameworkError::EventHandler { FrameworkError::EventHandler {
@ -36,13 +36,17 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) {
ctx: _, ctx: _,
event, event,
framework: _, framework: _,
..
} => { } => {
error!("Error while handling event {}:\n{error:?}", event.name()); error!(
"Error while handling event {}:\n{error:?}",
event.snake_case_name()
);
} }
error => { error => {
if let Err(e) = poise::builtins::on_error(error).await { if let Err(e) = poise::builtins::on_error(error).await {
error!("Unhandled error occured:\n{e:#?}"); error!("Unhandled error occurred:\n{e:#?}");
} }
} }
} }

View file

@ -3,7 +3,9 @@ use crate::Data;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use log::*; use log::*;
use poise::serenity_prelude::{Context, Message}; use poise::serenity_prelude::{
Context, CreateAllowedMentions, CreateEmbed, CreateMessage, Message,
};
mod issues; mod issues;
mod providers; mod providers;
@ -17,16 +19,16 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()>
let log = find_log(message).await; let log = find_log(message).await;
if log.is_err() { if log.is_err() {
channel let embed = CreateEmbed::new()
.send_message(ctx, |m| { .title("Analyze failed!")
m.reference_message(message) .description("Couldn't download log");
.allowed_mentions(|am| am.replied_user(true)) let allowed_mentions = CreateAllowedMentions::new().replied_user(true);
.embed(|e| { let our_message = CreateMessage::new()
e.title("Analyze failed!") .reference_message(message)
.description("Couldn't download log") .allowed_mentions(allowed_mentions)
}) .embed(embed);
})
.await?; channel.send_message(ctx, our_message).await?;
return Ok(()); return Ok(());
} }
@ -38,31 +40,32 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()>
let issues = find_issues(&log, data).await?; let issues = find_issues(&log, data).await?;
channel let embed = {
.send_message(ctx, |m| { let mut e = CreateEmbed::new().title("Log analysis");
m.reference_message(message)
.allowed_mentions(|am| am.replied_user(true))
.embed(|e| {
e.title("Log analysis");
if issues.is_empty() { if issues.is_empty() {
e.color(COLORS["green"]).field( e = e.color(COLORS["green"]).field(
"Analyze failed!", "Analyze failed!",
"No issues found automatically", "No issues found automatically",
false, false,
); );
} else { } else {
e.color(COLORS["red"]); e = e.color(COLORS["red"]);
for (title, description) in issues { for (title, description) in issues {
e.field(title, description, false); e = e.field(title, description, false);
} }
} }
e e
}) };
})
.await?; let allowed_mentions = CreateAllowedMentions::new().replied_user(true);
let message = CreateMessage::new()
.allowed_mentions(allowed_mentions)
.embed(embed);
channel.send_message(ctx, message).await?;
Ok(()) Ok(())
} }

View file

@ -13,7 +13,7 @@ pub async fn handle(ctx: &Context, reaction: &Reaction) -> Result<()> {
.wrap_err_with(|| "Couldn't fetch message from reaction!")?; .wrap_err_with(|| "Couldn't fetch message from reaction!")?;
if let Some(interaction) = &message.interaction { if let Some(interaction) = &message.interaction {
if interaction.kind == InteractionType::ApplicationCommand if interaction.kind == InteractionType::Command
&& interaction.user == user && interaction.user == user
&& reaction.emoji.unicode_eq("") && reaction.emoji.unicode_eq("")
{ {

View file

@ -1,27 +1,20 @@
use color_eyre::eyre::{eyre, Context as _, Result}; use color_eyre::eyre::Result;
use poise::serenity_prelude::{Context, Message}; use poise::serenity_prelude::{Context, CreateAllowedMentions, CreateMessage, Message};
use crate::utils; use crate::utils;
pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { pub async fn handle(ctx: &Context, message: &Message) -> Result<()> {
let embeds = utils::resolve_message(ctx, message).await?; let embeds = utils::resolve_message(ctx, message).await?;
// TOOD getchoo: actually reply to user // TODO getchoo: actually reply to user
// ...not sure why Message doesn't give me a builder in reply() or equivalents // ...not sure why Message doesn't give me a builder in reply() or equivalents
let our_channel = message
.channel(ctx)
.await
.wrap_err_with(|| "Couldn't get channel from message!")?
.guild()
.ok_or_else(|| eyre!("Couldn't convert to GuildChannel!"))?;
if !embeds.is_empty() { if !embeds.is_empty() {
our_channel let allowed_mentions = CreateAllowedMentions::new().replied_user(false);
.send_message(ctx, |m| { let reply = CreateMessage::new()
m.set_embeds(embeds) .embeds(embeds)
.allowed_mentions(|am| am.replied_user(false)) .allowed_mentions(allowed_mentions);
})
.await?; message.channel_id.send_message(ctx, reply).await?;
} }
Ok(()) Ok(())

View file

@ -2,8 +2,8 @@ use crate::{api, Data};
use color_eyre::eyre::{Report, Result}; use color_eyre::eyre::{Report, Result};
use log::*; use log::*;
use poise::serenity_prelude::{Activity, Context, OnlineStatus}; use poise::serenity_prelude::{ActivityData, Context, FullEvent, OnlineStatus};
use poise::{Event, FrameworkContext}; use poise::FrameworkContext;
mod analyze_logs; mod analyze_logs;
mod delete_on_reaction; mod delete_on_reaction;
@ -14,22 +14,22 @@ mod support_onboard;
pub async fn handle( pub async fn handle(
ctx: &Context, ctx: &Context,
event: &Event<'_>, event: &FullEvent,
_framework: FrameworkContext<'_, Data, Report>, _framework: FrameworkContext<'_, Data, Report>,
data: &Data, data: &Data,
) -> Result<()> { ) -> Result<()> {
match event { match event {
Event::Ready { data_about_bot } => { FullEvent::Ready { data_about_bot } => {
info!("Logged in as {}!", data_about_bot.user.name); info!("Logged in as {}!", data_about_bot.user.name);
let latest_minecraft_version = api::prism_meta::get_latest_minecraft_version().await?; let latest_minecraft_version = api::prism_meta::get_latest_minecraft_version().await?;
let activity = Activity::playing(format!("Minecraft {}", latest_minecraft_version)); let activity = ActivityData::playing(format!("Minecraft {}", latest_minecraft_version));
info!("Setting presence to activity {activity:#?}"); info!("Setting presence to activity {activity:#?}");
ctx.set_presence(Some(activity), OnlineStatus::Online).await; ctx.set_presence(Some(activity), OnlineStatus::Online);
} }
Event::Message { new_message } => { FullEvent::Message { new_message } => {
// ignore new messages from bots // ignore new messages from bots
// NOTE: the webhook_id check allows us to still respond to PK users // NOTE: the webhook_id check allows us to still respond to PK users
if new_message.author.bot && new_message.webhook_id.is_none() { if new_message.author.bot && new_message.webhook_id.is_none() {
@ -52,11 +52,13 @@ pub async fn handle(
analyze_logs::handle(ctx, new_message, data).await?; analyze_logs::handle(ctx, new_message, data).await?;
} }
Event::ReactionAdd { add_reaction } => { FullEvent::ReactionAdd { add_reaction } => {
delete_on_reaction::handle(ctx, add_reaction).await? delete_on_reaction::handle(ctx, add_reaction).await?;
} }
Event::ThreadCreate { thread } => support_onboard::handle(ctx, thread).await?, FullEvent::ThreadCreate { thread } => {
support_onboard::handle(ctx, thread).await?;
}
_ => {} _ => {}
} }

View file

@ -1,22 +1,23 @@
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{eyre, Result};
use log::*; use log::*;
use poise::serenity_prelude::{ChannelType, Context, GuildChannel}; use poise::serenity_prelude::{
ChannelType, Context, CreateAllowedMentions, CreateMessage, GuildChannel,
};
pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> { pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> {
if thread.kind != ChannelType::PublicThread { if thread.kind != ChannelType::PublicThread {
debug!("Not doing support onboard in non-thread channel");
return Ok(()); return Ok(());
} }
let parent_id = thread if thread
.parent_id .parent_id
.ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))?; .ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))?
.name(ctx)
let parent_channel = ctx .await
.cache .unwrap_or("".to_string())
.guild_channel(parent_id) != "support"
.ok_or_else(|| eyre!("Couldn't get GuildChannel {}!", parent_id))?; {
if parent_channel.name != "support" {
debug!("Not posting onboarding message to threads outside of support"); debug!("Not posting onboarding message to threads outside of support");
return Ok(()); return Ok(());
} }
@ -32,12 +33,15 @@ pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> {
"Please don't ping people for support questions, unless you have their permission." "Please don't ping people for support questions, unless you have their permission."
); );
thread let allowed_mentions = CreateAllowedMentions::new()
.send_message(ctx, |m| { .replied_user(true)
m.content(msg) .users(Vec::from([owner]));
.allowed_mentions(|am| am.replied_user(true).users(Vec::from([owner])))
}) let message = CreateMessage::new()
.await?; .content(msg)
.allowed_mentions(allowed_mentions);
thread.send_message(ctx, message).await?;
Ok(()) Ok(())
} }

View file

@ -10,8 +10,6 @@ use poise::{
serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions, serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions,
}; };
use serenity::ShardManager;
use redis::ConnectionLike; use redis::ConnectionLike;
use tokio::signal::ctrl_c; use tokio::signal::ctrl_c;
@ -19,7 +17,6 @@ use tokio::signal::ctrl_c;
use tokio::signal::unix::{signal, SignalKind}; use tokio::signal::unix::{signal, SignalKind};
#[cfg(target_family = "windows")] #[cfg(target_family = "windows")]
use tokio::signal::windows::ctrl_close; use tokio::signal::windows::ctrl_close;
use tokio::sync::Mutex;
mod api; mod api;
mod commands; mod commands;
@ -78,9 +75,9 @@ async fn setup(
Ok(data) Ok(data)
} }
async fn handle_shutdown(shard_manager: Arc<Mutex<ShardManager>>, reason: &str) { async fn handle_shutdown(shard_manager: Arc<serenity::ShardManager>, reason: &str) {
warn!("{reason}! Shutting down bot..."); warn!("{reason}! Shutting down bot...");
shard_manager.lock().await.shutdown_all().await; shard_manager.shutdown_all().await;
println!("{}", "Everything is shutdown. Goodbye!".green()) println!("{}", "Everything is shutdown. Goodbye!".green())
} }
@ -111,7 +108,9 @@ async fn main() -> Result<()> {
prefix_options: PrefixFrameworkOptions { prefix_options: PrefixFrameworkOptions {
prefix: Some("r".into()), prefix: Some("r".into()),
edit_tracker: Some(EditTracker::for_timespan(Duration::from_secs(3600))), edit_tracker: Some(Arc::from(EditTracker::for_timespan(Duration::from_secs(
3600,
)))),
..Default::default() ..Default::default()
}, },
@ -119,22 +118,23 @@ async fn main() -> Result<()> {
}; };
let framework = Framework::builder() let framework = Framework::builder()
.token(token)
.intents(intents)
.options(options) .options(options)
.setup(|ctx, ready, framework| Box::pin(setup(ctx, ready, framework))) .setup(|ctx, ready, framework| Box::pin(setup(ctx, ready, framework)))
.build() .build();
.await
.wrap_err_with(|| "Failed to build framework!")?; let mut client = serenity::ClientBuilder::new(token, intents)
.framework(framework)
.await?;
let shard_manager = client.shard_manager.clone();
let shard_manager = framework.shard_manager().clone();
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
let mut sigterm = signal(SignalKind::terminate())?; let mut sigterm = signal(SignalKind::terminate())?;
#[cfg(target_family = "windows")] #[cfg(target_family = "windows")]
let mut sigterm = ctrl_close()?; let mut sigterm = ctrl_close()?;
tokio::select! { tokio::select! {
result = framework.start() => result.map_err(Report::from), result = client.start() => result.map_err(Report::from),
_ = sigterm.recv() => { _ = sigterm.recv() => {
handle_shutdown(shard_manager, "Received SIGTERM").await; handle_shutdown(shard_manager, "Received SIGTERM").await;
std::process::exit(0); std::process::exit(0);

View file

@ -69,7 +69,7 @@ impl Storage {
Ok(()) Ok(())
} }
async fn expire_key(&self, key: &str, expire_seconds: usize) -> Result<()> { async fn expire_key(&self, key: &str, expire_seconds: i64) -> Result<()> {
debug!("Expiring key {key} in {expire_seconds}"); debug!("Expiring key {key} in {expire_seconds}");
let mut con = self.client.get_async_connection().await?; let mut con = self.client.get_async_connection().await?;

View file

@ -1,7 +1,12 @@
use std::str::FromStr;
use color_eyre::eyre::{eyre, Context as _, Result}; use color_eyre::eyre::{eyre, Context as _, Result};
use log::*; use log::*;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use poise::serenity_prelude::{ChannelType, Colour, Context, CreateEmbed, Message}; use poise::serenity_prelude::{
ChannelId, ChannelType, Colour, Context, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter,
Message, MessageId,
};
use regex::Regex; use regex::Regex;
static MESSAGE_PATTERN: Lazy<Regex> = Lazy::new(|| { static MESSAGE_PATTERN: Lazy<Regex> = Lazy::new(|| {
@ -21,103 +26,84 @@ pub fn find_first_image(msg: &Message) -> Option<String> {
} }
pub async fn resolve(ctx: &Context, msg: &Message) -> Result<Vec<CreateEmbed>> { pub async fn resolve(ctx: &Context, msg: &Message) -> Result<Vec<CreateEmbed>> {
let matches = MESSAGE_PATTERN.captures_iter(&msg.content); let matches = MESSAGE_PATTERN
.captures_iter(&msg.content)
.map(|capture| capture.extract());
let mut embeds: Vec<CreateEmbed> = vec![]; let mut embeds: Vec<CreateEmbed> = vec![];
for captured in matches.take(3) { for (_, [_server_id, channel_id, message_id]) in matches {
// don't leak messages from other servers let channel = ChannelId::from_str(channel_id)
if let Some(server_id) = captured.get(0) { .wrap_err_with(|| format!("Couldn't parse channel ID {channel_id}!"))?
let other_server: u64 = server_id.as_str().parse().unwrap_or_default(); .to_channel_cached(ctx.as_ref())
let current_id = msg.guild_id.unwrap_or_default(); .ok_or_else(|| eyre!("Couldn't find Guild Channel from {channel_id}!"))?
.to_owned();
if &other_server != current_id.as_u64() { let author_can_view = if channel.kind == ChannelType::PublicThread
debug!("Not resolving message of other guild."); || channel.kind == ChannelType::PrivateThread
continue; {
} let thread_members = channel
} else { .id
warn!("Couldn't find server_id from Discord link! Not resolving message to be safe"); .get_thread_members(ctx)
continue; .await
} .wrap_err("Couldn't get members from thread!")?;
if let Some(channel_id) = captured.get(1) { thread_members
let parsed: u64 = channel_id.as_str().parse().unwrap_or_default();
let req_channel = ctx
.cache
.channel(parsed)
.ok_or_else(|| eyre!("Couldn't get channel_id from Discord regex!"))?
.guild()
.ok_or_else(|| {
eyre!("Couldn't convert to GuildChannel from channel_id {parsed}!")
})?;
if !req_channel.is_text_based() {
debug!("Not resolving message is non-text-based channel.");
continue;
}
if req_channel.kind == ChannelType::PrivateThread {
if let Some(id) = req_channel.parent_id {
let parent = ctx.cache.guild_channel(id).ok_or_else(|| {
eyre!("Couldn't get parent channel {id} for thread {req_channel}!")
})?;
let parent_members = parent.members(ctx).await.unwrap_or_default();
if !parent_members.iter().any(|m| m.user.id == msg.author.id) {
debug!("Not resolving message for user not a part of a private thread.");
continue;
}
}
} else if req_channel
.members(ctx)
.await?
.iter() .iter()
.any(|m| m.user.id == msg.author.id) .any(|member| member.user_id == msg.author.id)
{ } else {
debug!("Not resolving for message for user not a part of a channel"); channel
continue; .members(ctx)
} .wrap_err_with(|| format!("Couldn't get members for channel {channel_id}!"))?
.iter()
.any(|member| member.user.id == msg.author.id)
};
let message_id: u64 = captured if !author_can_view {
.get(2) debug!("Not resolving message for author who can't see it");
.ok_or_else(|| eyre!("Couldn't get message_id from Discord regex!"))?
.as_str()
.parse()
.wrap_err_with(|| {
eyre!("Couldn't parse message_id from Discord regex as a MessageId!")
})?;
let original_message = req_channel.message(ctx, message_id).await?;
let mut embed = CreateEmbed::default();
embed
.author(|a| {
a.name(original_message.author.tag())
.icon_url(original_message.author.default_avatar_url())
})
.color(Colour::BLITZ_BLUE)
.timestamp(original_message.timestamp)
.footer(|f| f.text(format!("#{}", req_channel.name)))
.description(format!(
"{}\n\n[Jump to original message]({})",
original_message.content,
original_message.link()
));
if !original_message.attachments.is_empty() {
embed.fields(original_message.attachments.iter().map(|a| {
(
"Attachments".to_string(),
format!("[{}]({})", a.filename, a.url),
false,
)
}));
if let Some(image) = find_first_image(msg) {
embed.image(image);
}
}
embeds.push(embed);
} }
let original_message = channel
.message(
ctx,
MessageId::from_str(message_id)
.wrap_err_with(|| format!("Couldn't parse message ID {message_id}!"))?,
)
.await
.wrap_err_with(|| {
format!("Couldn't get message from ID {message_id} in channel {channel_id}!")
})?;
let author = CreateEmbedAuthor::new(original_message.author.tag())
.icon_url(original_message.author.default_avatar_url());
let footer = CreateEmbedFooter::new(format!("#{}", channel.name));
let mut embed = CreateEmbed::new()
.author(author)
.color(Colour::BLITZ_BLUE)
.timestamp(original_message.timestamp)
.footer(footer)
.description(format!(
"{}\n\n[Jump to original message]({})",
original_message.content,
original_message.link()
));
if !original_message.attachments.is_empty() {
embed = embed.fields(original_message.attachments.iter().map(|a| {
(
"Attachments".to_string(),
format!("[{}]({})", a.filename, a.url),
false,
)
}));
if let Some(image) = find_first_image(msg) {
embed = embed.image(image);
}
}
embeds.push(embed);
} }
Ok(embeds) Ok(embeds)