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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,8 @@ use std::env;
use color_eyre::eyre::{eyre, Result};
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"));
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;
ctx.send(|m| {
if let Some(user) = user {
m.content(format!("<@{}>", user.id));
let embed = {
let mut e = CreateEmbed::new();
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| {
e.title(&frontmatter.title);
e.description(&tag.content);
if let Some(image) = &frontmatter.image {
e = e.image(image);
}
if let Some(color) = &frontmatter.color {
let color = *consts::COLORS
.get(color.as_str())
.unwrap_or(&Color::default());
e.color(color);
if let Some(fields) = &frontmatter.fields {
for field in fields {
e = e.field(&field.name, &field.value, field.inline);
}
}
if let Some(image) = &frontmatter.image {
e.image(image);
}
e
};
if let Some(fields) = &frontmatter.fields {
for field in fields {
e.field(&field.name, &field.value, field.inline);
}
}
let reply = {
let mut r = CreateReply::default();
e
})
})
.await?;
if let Some(user) = user {
r = r.content(format!("<@{}>", user.id));
}
r.embed(embed)
};
ctx.send(reply).await?;
Ok(())
}

View file

@ -3,8 +3,8 @@ use crate::Data;
use color_eyre::eyre::Report;
use log::*;
use poise::serenity_prelude::Timestamp;
use poise::FrameworkError;
use poise::serenity_prelude::{CreateEmbed, Timestamp};
use poise::{CreateReply, FrameworkError};
pub async fn handle(error: FrameworkError<'_, Data, Report>) {
match error {
@ -12,23 +12,23 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) {
error, framework, ..
} => {
error!("Error setting up client! Bailing out");
framework.shard_manager().lock().await.shutdown_all().await;
framework.shard_manager().shutdown_all().await;
panic!("{error}")
}
FrameworkError::Command { error, ctx } => {
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["red"])
})
})
.await
.ok();
let embed = CreateEmbed::new()
.title("Something went wrong!")
.description("oopsie")
.timestamp(Timestamp::now())
.color(COLORS["red"]);
let reply = CreateReply::default().embed(embed);
ctx.send(reply).await.ok();
}
FrameworkError::EventHandler {
@ -36,13 +36,17 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) {
ctx: _,
event,
framework: _,
..
} => {
error!("Error while handling event {}:\n{error:?}", event.name());
error!(
"Error while handling event {}:\n{error:?}",
event.snake_case_name()
);
}
error => {
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 log::*;
use poise::serenity_prelude::{Context, Message};
use poise::serenity_prelude::{
Context, CreateAllowedMentions, CreateEmbed, CreateMessage, Message,
};
mod issues;
mod providers;
@ -17,16 +19,16 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()>
let log = find_log(message).await;
if log.is_err() {
channel
.send_message(ctx, |m| {
m.reference_message(message)
.allowed_mentions(|am| am.replied_user(true))
.embed(|e| {
e.title("Analyze failed!")
.description("Couldn't download log")
})
})
.await?;
let embed = CreateEmbed::new()
.title("Analyze failed!")
.description("Couldn't download log");
let allowed_mentions = CreateAllowedMentions::new().replied_user(true);
let our_message = CreateMessage::new()
.reference_message(message)
.allowed_mentions(allowed_mentions)
.embed(embed);
channel.send_message(ctx, our_message).await?;
return Ok(());
}
@ -38,31 +40,32 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()>
let issues = find_issues(&log, data).await?;
channel
.send_message(ctx, |m| {
m.reference_message(message)
.allowed_mentions(|am| am.replied_user(true))
.embed(|e| {
e.title("Log analysis");
let embed = {
let mut e = CreateEmbed::new().title("Log analysis");
if issues.is_empty() {
e.color(COLORS["green"]).field(
"Analyze failed!",
"No issues found automatically",
false,
);
} else {
e.color(COLORS["red"]);
if issues.is_empty() {
e = e.color(COLORS["green"]).field(
"Analyze failed!",
"No issues found automatically",
false,
);
} else {
e = e.color(COLORS["red"]);
for (title, description) in issues {
e.field(title, description, false);
}
}
for (title, description) in issues {
e = e.field(title, description, false);
}
}
e
})
})
.await?;
e
};
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(())
}

View file

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

View file

@ -1,27 +1,20 @@
use color_eyre::eyre::{eyre, Context as _, Result};
use poise::serenity_prelude::{Context, Message};
use color_eyre::eyre::Result;
use poise::serenity_prelude::{Context, CreateAllowedMentions, CreateMessage, Message};
use crate::utils;
pub async fn handle(ctx: &Context, message: &Message) -> Result<()> {
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
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() {
our_channel
.send_message(ctx, |m| {
m.set_embeds(embeds)
.allowed_mentions(|am| am.replied_user(false))
})
.await?;
let allowed_mentions = CreateAllowedMentions::new().replied_user(false);
let reply = CreateMessage::new()
.embeds(embeds)
.allowed_mentions(allowed_mentions);
message.channel_id.send_message(ctx, reply).await?;
}
Ok(())

View file

@ -2,8 +2,8 @@ use crate::{api, Data};
use color_eyre::eyre::{Report, Result};
use log::*;
use poise::serenity_prelude::{Activity, Context, OnlineStatus};
use poise::{Event, FrameworkContext};
use poise::serenity_prelude::{ActivityData, Context, FullEvent, OnlineStatus};
use poise::FrameworkContext;
mod analyze_logs;
mod delete_on_reaction;
@ -14,22 +14,22 @@ mod support_onboard;
pub async fn handle(
ctx: &Context,
event: &Event<'_>,
event: &FullEvent,
_framework: FrameworkContext<'_, Data, Report>,
data: &Data,
) -> Result<()> {
match event {
Event::Ready { data_about_bot } => {
FullEvent::Ready { data_about_bot } => {
info!("Logged in as {}!", data_about_bot.user.name);
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:#?}");
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
// NOTE: the webhook_id check allows us to still respond to PK users
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?;
}
Event::ReactionAdd { add_reaction } => {
delete_on_reaction::handle(ctx, add_reaction).await?
FullEvent::ReactionAdd { add_reaction } => {
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 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<()> {
if thread.kind != ChannelType::PublicThread {
debug!("Not doing support onboard in non-thread channel");
return Ok(());
}
let parent_id = thread
if thread
.parent_id
.ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))?;
let parent_channel = ctx
.cache
.guild_channel(parent_id)
.ok_or_else(|| eyre!("Couldn't get GuildChannel {}!", parent_id))?;
if parent_channel.name != "support" {
.ok_or_else(|| eyre!("Couldn't get parent ID from thread {}!", thread.name))?
.name(ctx)
.await
.unwrap_or("".to_string())
!= "support"
{
debug!("Not posting onboarding message to threads outside of support");
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."
);
thread
.send_message(ctx, |m| {
m.content(msg)
.allowed_mentions(|am| am.replied_user(true).users(Vec::from([owner])))
})
.await?;
let allowed_mentions = CreateAllowedMentions::new()
.replied_user(true)
.users(Vec::from([owner]));
let message = CreateMessage::new()
.content(msg)
.allowed_mentions(allowed_mentions);
thread.send_message(ctx, message).await?;
Ok(())
}

View file

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

View file

@ -69,7 +69,7 @@ impl Storage {
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}");
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 log::*;
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;
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>> {
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![];
for captured in matches.take(3) {
// don't leak messages from other servers
if let Some(server_id) = captured.get(0) {
let other_server: u64 = server_id.as_str().parse().unwrap_or_default();
let current_id = msg.guild_id.unwrap_or_default();
for (_, [_server_id, channel_id, message_id]) in matches {
let channel = ChannelId::from_str(channel_id)
.wrap_err_with(|| format!("Couldn't parse channel ID {channel_id}!"))?
.to_channel_cached(ctx.as_ref())
.ok_or_else(|| eyre!("Couldn't find Guild Channel from {channel_id}!"))?
.to_owned();
if &other_server != current_id.as_u64() {
debug!("Not resolving message of other guild.");
continue;
}
} else {
warn!("Couldn't find server_id from Discord link! Not resolving message to be safe");
continue;
}
let author_can_view = if channel.kind == ChannelType::PublicThread
|| channel.kind == ChannelType::PrivateThread
{
let thread_members = channel
.id
.get_thread_members(ctx)
.await
.wrap_err("Couldn't get members from thread!")?;
if let Some(channel_id) = captured.get(1) {
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?
thread_members
.iter()
.any(|m| m.user.id == msg.author.id)
{
debug!("Not resolving for message for user not a part of a channel");
continue;
}
.any(|member| member.user_id == msg.author.id)
} else {
channel
.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
.get(2)
.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);
if !author_can_view {
debug!("Not resolving message for author who can't see it");
}
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)