refactor!: use poise 0.6.1
This commit is contained in:
parent
203ba111cc
commit
7252ced3cb
16 changed files with 700 additions and 512 deletions
642
Cargo.lock
generated
642
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
28
Cargo.toml
28
Cargo.toml
|
@ -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",
|
||||||
|
|
|
@ -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);
|
||||||
})
|
|
||||||
.await?;
|
ctx.send(reply).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
embed
|
||||||
|
.title("Rory :3")
|
||||||
.url(&rory.url)
|
.url(&rory.url)
|
||||||
.image(rory.url)
|
.image(rory.url)
|
||||||
.footer(|f| f.text(format!("ID {}", rory.id)))
|
.footer(footer)
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
})
|
|
||||||
.await?;
|
let reply = CreateReply::default().embed(embed);
|
||||||
|
ctx.send(reply).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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| {
|
|
||||||
m.embed(|e| {
|
|
||||||
e.title("Say command used!")
|
|
||||||
.description(content)
|
.description(content)
|
||||||
.author(|a| {
|
.author(author);
|
||||||
a.name(ctx.author().tag()).icon_url(
|
|
||||||
ctx.author().avatar_url().unwrap_or("undefined".to_string()),
|
let message = CreateMessage::new().embed(embed);
|
||||||
)
|
log_channel.1.send_message(ctx, message).await?;
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
|
|
||||||
m.embed(|e| {
|
|
||||||
e.title(&frontmatter.title);
|
|
||||||
e.description(&tag.content);
|
|
||||||
|
|
||||||
if let Some(color) = &frontmatter.color {
|
if let Some(color) = &frontmatter.color {
|
||||||
let color = *consts::COLORS
|
let color = *consts::COLORS
|
||||||
.get(color.as_str())
|
.get(color.as_str())
|
||||||
.unwrap_or(&Color::default());
|
.unwrap_or(&Color::default());
|
||||||
e.color(color);
|
e = e.color(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(image) = &frontmatter.image {
|
if let Some(image) = &frontmatter.image {
|
||||||
e.image(image);
|
e = e.image(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(fields) = &frontmatter.fields {
|
if let Some(fields) = &frontmatter.fields {
|
||||||
for field in fields {
|
for field in fields {
|
||||||
e.field(&field.name, &field.value, field.inline);
|
e = e.field(&field.name, &field.value, field.inline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e
|
e
|
||||||
})
|
};
|
||||||
})
|
|
||||||
.await?;
|
let reply = {
|
||||||
|
let mut r = CreateReply::default();
|
||||||
|
|
||||||
|
if let Some(user) = user {
|
||||||
|
r = r.content(format!("<@{}>", user.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
r.embed(embed)
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.send(reply).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:#?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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("❌")
|
||||||
{
|
{
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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?;
|
||||||
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
26
src/main.rs
26
src/main.rs
|
@ -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);
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
|
@ -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,81 +26,63 @@ 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;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!("Couldn't find server_id from Discord link! Not resolving message to be safe");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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?
|
|
||||||
.iter()
|
|
||||||
.any(|m| m.user.id == msg.author.id)
|
|
||||||
{
|
{
|
||||||
debug!("Not resolving for message for user not a part of a channel");
|
let thread_members = channel
|
||||||
continue;
|
.id
|
||||||
|
.get_thread_members(ctx)
|
||||||
|
.await
|
||||||
|
.wrap_err("Couldn't get members from thread!")?;
|
||||||
|
|
||||||
|
thread_members
|
||||||
|
.iter()
|
||||||
|
.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)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !author_can_view {
|
||||||
|
debug!("Not resolving message for author who can't see it");
|
||||||
}
|
}
|
||||||
|
|
||||||
let message_id: u64 = captured
|
let original_message = channel
|
||||||
.get(2)
|
.message(
|
||||||
.ok_or_else(|| eyre!("Couldn't get message_id from Discord regex!"))?
|
ctx,
|
||||||
.as_str()
|
MessageId::from_str(message_id)
|
||||||
.parse()
|
.wrap_err_with(|| format!("Couldn't parse message ID {message_id}!"))?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
.wrap_err_with(|| {
|
.wrap_err_with(|| {
|
||||||
eyre!("Couldn't parse message_id from Discord regex as a MessageId!")
|
format!("Couldn't get message from ID {message_id} in channel {channel_id}!")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let original_message = req_channel.message(ctx, message_id).await?;
|
let author = CreateEmbedAuthor::new(original_message.author.tag())
|
||||||
let mut embed = CreateEmbed::default();
|
.icon_url(original_message.author.default_avatar_url());
|
||||||
embed
|
let footer = CreateEmbedFooter::new(format!("#{}", channel.name));
|
||||||
.author(|a| {
|
|
||||||
a.name(original_message.author.tag())
|
let mut embed = CreateEmbed::new()
|
||||||
.icon_url(original_message.author.default_avatar_url())
|
.author(author)
|
||||||
})
|
|
||||||
.color(Colour::BLITZ_BLUE)
|
.color(Colour::BLITZ_BLUE)
|
||||||
.timestamp(original_message.timestamp)
|
.timestamp(original_message.timestamp)
|
||||||
.footer(|f| f.text(format!("#{}", req_channel.name)))
|
.footer(footer)
|
||||||
.description(format!(
|
.description(format!(
|
||||||
"{}\n\n[Jump to original message]({})",
|
"{}\n\n[Jump to original message]({})",
|
||||||
original_message.content,
|
original_message.content,
|
||||||
|
@ -103,7 +90,7 @@ pub async fn resolve(ctx: &Context, msg: &Message) -> Result<Vec<CreateEmbed>> {
|
||||||
));
|
));
|
||||||
|
|
||||||
if !original_message.attachments.is_empty() {
|
if !original_message.attachments.is_empty() {
|
||||||
embed.fields(original_message.attachments.iter().map(|a| {
|
embed = embed.fields(original_message.attachments.iter().map(|a| {
|
||||||
(
|
(
|
||||||
"Attachments".to_string(),
|
"Attachments".to_string(),
|
||||||
format!("[{}]({})", a.filename, a.url),
|
format!("[{}]({})", a.filename, a.url),
|
||||||
|
@ -112,13 +99,12 @@ pub async fn resolve(ctx: &Context, msg: &Message) -> Result<Vec<CreateEmbed>> {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if let Some(image) = find_first_image(msg) {
|
if let Some(image) = find_first_image(msg) {
|
||||||
embed.image(image);
|
embed = embed.image(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
embeds.push(embed);
|
embeds.push(embed);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(embeds)
|
Ok(embeds)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue