build: Bump Serenity

This commit is contained in:
oSumAtrIX 2023-10-26 17:46:43 +02:00
parent 21dd21c377
commit a8e9a9bb49
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
19 changed files with 929 additions and 936 deletions

899
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ homepage = "https://revanced.app"
license = "GPL-3.0"
name = "revanced-discord-bot"
repository = "https://github.com/revanced/revanced-discord-bot"
version = "2.5.1"
version = "2.5.2"
edition = "2021"
[profile.release]
@ -17,9 +17,9 @@ panic = "abort"
[dependencies]
bson = "2.4"
serde_with_macros = "2.0.1"
serde_with_macros = "3.4.0"
mongodb = "2.4.0"
poise = "0.5.2"
poise = { git = "https://github.com/serenity-rs/poise.git", branch = "serenity-next" }
decancer = "1.5.4"
tokio = { version = "1.26.0", features = ["rt-multi-thread"] }
dotenv = "0.15.0"
@ -27,10 +27,16 @@ serde = { version = "1.0.158", features = ["derive"] }
serde_json = "1.0.94"
regex = "1.7.3"
serde_regex = "1.1.0"
reqwest = { version = "0.11.15", features= ["rustls-tls"], default-features = false }
reqwest = { version = "0.11.15", features = [
"rustls-tls",
"json",
], default-features = false }
chrono = "0.4.24"
dirs = "5.0.0"
tracing = { version = "0.1.37", features = ["max_level_debug", "release_max_level_info"] }
tracing = { version = "0.1.37", features = [
"max_level_debug",
"release_max_level_info",
] }
tracing-subscriber = "0.3.16"
hmac-sha256 = "1.1.6"
base64 = "0.21.0"

View File

@ -1,3 +1,5 @@
use poise::serenity_prelude::CreateEmbed;
use poise::CreateReply;
use tracing::debug;
use crate::utils::bot::load_configuration;
@ -15,12 +17,13 @@ pub async fn reload(ctx: Context<'_>) -> Result<(), Error> {
debug!("{} reloaded the configuration.", ctx.author().name);
ctx.send(|f| {
f.ephemeral(true).embed(|f| {
f.description("Successfully reloaded configuration.")
.color(embed_color)
})
})
ctx.send(
CreateReply::new().ephemeral(true).embed(
CreateEmbed::new()
.description("Successfully reloaded configuration.")
.color(embed_color),
),
)
.await?;
Ok(())
@ -32,18 +35,17 @@ pub async fn stop(ctx: Context<'_>) -> Result<(), Error> {
debug!("{} stopped the bot.", ctx.author().name);
let color = ctx.data().read().await.configuration.general.embed_color;
ctx.send(|f| {
f.ephemeral(true)
.embed(|f| f.description("Stopped the bot.").color(color))
})
ctx.send(
CreateReply::new().ephemeral(true).embed(
CreateEmbed::new()
.description("Stopped the bot.")
.color(color),
),
)
.await?;
ctx.framework()
.shard_manager()
.lock()
.await
.shutdown_all()
.await;
ctx.framework().shard_manager().shutdown_all().await;
Ok(())
}

View File

@ -1,5 +1,5 @@
use poise::serenity_prelude::{self as serenity, MessageId, ParseValue, ReactionType};
use poise::ReplyHandle;
use poise::serenity_prelude::{CreateActionRow, CreateAllowedMentions, CreateButton, ReactionType};
use poise::{CreateReply, ReplyHandle};
use crate::utils::message::clone_message;
use crate::{Context, Error};
@ -14,8 +14,9 @@ pub async fn reply(
async fn send_ephermal<'a>(
ctx: &Context<'a>,
content: &str,
) -> Result<ReplyHandle<'a>, serenity::Error> {
ctx.send(|f| f.ephemeral(true).content(content)).await
) -> Result<ReplyHandle<'a>, poise::serenity_prelude::Error> {
ctx.send(CreateReply::new().ephemeral(true).content(content))
.await
}
let http = &ctx.serenity_context().http;
@ -23,7 +24,7 @@ pub async fn reply(
if let Some(reply_message) = reply_message {
if let Ok(reply_message) = reply_message.parse::<u64>() {
match channel.message(http, MessageId(reply_message)).await {
match channel.message(http, reply_message).await {
Ok(reply_message) => {
reply_message.reply(http, &message).await?;
},
@ -71,29 +72,24 @@ pub async fn poll(
let message = ctx
.serenity_context()
.http
.get_message(channel_id, message_id)
.get_message(channel_id.into(), message_id.into())
.await?;
ctx.send(|m| {
let mut clone = clone_message(&message, m).components(|c| {
c.create_action_row(|r| {
r.create_button(|b| {
b.label("Vote")
.emoji(ReactionType::Unicode("🗳️".to_string()))
.custom_id(format!("poll:{id}:{age}"))
})
})
});
let message = clone_message(&message).components(vec![CreateActionRow::Buttons(vec![
CreateButton::new(format!("poll:{id}:{age}"))
.label("Vote")
.emoji(ReactionType::Unicode("🗳️".to_string())),
])]);
if ping {
clone = clone.allowed_mentions(|am| {
am.parse(ParseValue::Users)
.parse(ParseValue::Roles)
.parse(ParseValue::Everyone)
});
}
clone
ctx.send(if ping {
message.allowed_mentions(
CreateAllowedMentions::default()
.all_roles(true)
.all_users(true)
.everyone(true),
)
} else {
message
})
.await?;

View File

@ -1,16 +1,22 @@
use bson::{doc, Document};
use chrono::Utc;
use mongodb::options::{UpdateModifications, UpdateOptions};
use poise::serenity_prelude::{
self as serenity,
CreateEmbed,
CreateEmbedFooter,
EditMessage,
GetMessages,
Mentionable,
PermissionOverwrite,
Permissions,
UserId,
};
use tracing::{debug, error, trace};
use poise::CreateReply;
use tracing::{debug, trace};
use crate::db::model::{LockedChannel, Muted};
use crate::db::model::{Muted};
use crate::utils::bot::get_member;
use crate::utils::macros::to_user;
use crate::utils::moderation::{
@ -23,145 +29,6 @@ use crate::utils::moderation::{
use crate::utils::parse_duration;
use crate::{Context, Error};
/// Lock a channel.
#[poise::command(slash_command)]
pub async fn lock(ctx: Context<'_>) -> Result<(), Error> {
let data = &ctx.data().read().await;
let configuration = &data.configuration;
let database = &data.database;
let discord = &ctx.serenity_context();
let cache = &discord.cache;
let http = &discord.http;
let channel_id = ctx.channel_id().0;
let channel = &cache.guild_channel(channel_id).unwrap();
let author = ctx.author();
let query: Document = LockedChannel {
channel_id: Some(channel_id.to_string()),
..Default::default()
}
.into();
// Check if channel is already muted, if so succeed.
if let Ok(mut cursor) = database
.find::<LockedChannel>("locked", query.clone(), None)
.await
{
if cursor.advance().await.unwrap() {
respond_moderation(
&ctx,
&ModerationKind::Lock(
channel.clone(),
author.clone(),
Some(Error::from("Channel already locked")),
),
configuration,
)
.await?;
return Ok(());
}
}
// accumulate all roles with write permissions
let permission_overwrites: Vec<_> = channel
.permission_overwrites
.iter()
.filter_map(|r| {
if r.allow.send_messages() || !r.deny.send_messages() {
Some(r.clone())
} else {
None
}
})
.collect();
// save the original overwrites
let updated: Document = LockedChannel {
overwrites: Some(permission_overwrites.clone()),
..Default::default()
}
.into();
database
.update::<LockedChannel>(
"locked",
query,
UpdateModifications::Document(doc! { "$set": updated}),
Some(UpdateOptions::builder().upsert(true).build()),
)
.await?;
// lock the channel by and creating the new permission overwrite
for permission_overwrite in &permission_overwrites {
let permission = Permissions::SEND_MESSAGES & Permissions::ADD_REACTIONS;
if let Err(err) = channel
.create_permission(http, &PermissionOverwrite {
allow: permission_overwrite.allow & !permission,
deny: permission_overwrite.deny | permission,
kind: permission_overwrite.kind,
})
.await
{
error!("Failed to create the new permission: {:?}", err);
}
}
respond_moderation(
&ctx,
&ModerationKind::Lock(channel.clone(), author.clone(), None),
configuration,
)
.await
}
/// Unlock a channel.
#[poise::command(slash_command)]
pub async fn unlock(ctx: Context<'_>) -> Result<(), Error> {
let data = &ctx.data().read().await;
let configuration = &data.configuration;
let database = &data.database;
let discord = &ctx.serenity_context();
let cache = &discord.cache;
let http = &discord.http;
let channel_id = ctx.channel_id().0;
let delete_result = database
.find_and_delete::<LockedChannel>(
"locked",
LockedChannel {
channel_id: Some(channel_id.to_string()),
..Default::default()
}
.into(),
None,
)
.await;
let channel = cache.guild_channel(channel_id).unwrap();
let author = ctx.author();
let mut error = None;
if let Ok(Some(locked_channel)) = delete_result {
for overwrite in &locked_channel.overwrites.unwrap() {
channel.create_permission(http, overwrite).await?;
}
} else {
error = Some(Error::from("Channel already unlocked"))
}
respond_moderation(
&ctx,
&ModerationKind::Unlock(channel.clone(), author.clone(), error), // TODO: handle error
configuration,
)
.await
}
/// Unmute a member.
#[poise::command(slash_command)]
pub async fn unmute(
@ -175,8 +42,8 @@ pub async fn unmute(
let data = &ctx.data().read().await;
let configuration = &data.configuration;
if let Some(pending_unmute) = data.pending_unmutes.get(&id.0) {
trace!("Cancelling pending unmute for {}", id.0);
if let Some(pending_unmute) = data.pending_unmutes.get(&id.get()) {
trace!("Cancelling pending unmute for {}", id);
pending_unmute.abort();
}
@ -231,7 +98,7 @@ pub async fn mute(
};
let mut updated = Muted {
guild_id: Some(guild_id.0.to_string()),
guild_id: Some(guild_id.to_string()),
expires: unmute_time,
reason: Some(reason.clone()),
..Default::default()
@ -265,8 +132,8 @@ pub async fn mute(
)
.await?;
if let Some(pending_unmute) = data.pending_unmutes.get(&id.0) {
trace!("Cancelling pending unmute for {}", id.0);
if let Some(pending_unmute) = data.pending_unmutes.get(&id.get()) {
trace!("Cancelling pending unmute for {}", id);
pending_unmute.abort();
}
@ -281,7 +148,7 @@ pub async fn mute(
.await?;
} else {
data.pending_unmutes.insert(
id.0,
id.get(),
queue_unmute_member(
discord.clone(),
data.database.clone(),
@ -341,14 +208,15 @@ pub async fn purge(
let author = ctx.author();
let handle = ctx
.send(|f| {
f.embed(|f| {
f.title("Purging messages")
.send(
CreateReply::new().embed(
CreateEmbed::new()
.title("Purging messages")
.description("Accumulating...")
.color(embed_color)
.thumbnail(&image)
})
})
.thumbnail(&image),
),
)
.await?;
let mut response = handle.message().await?;
@ -361,9 +229,12 @@ pub async fn purge(
loop {
// Filter out messages that are too old
let mut messages = channel
.messages(&ctx.serenity_context(), |m| {
m.limit(count_to_delete as u64).before(response.id)
})
.messages(
&ctx.serenity_context(),
GetMessages::new()
.limit(count_to_delete as u8)
.before(response.id),
)
.await?
.into_iter()
.take_while(|m| m.timestamp.timestamp() > too_old_timestamp)
@ -384,7 +255,7 @@ pub async fn purge(
if let Ok(message_id) = message_id.parse::<u64>() {
messages = messages
.into_iter()
.take_while(|m| m.id.0 > message_id)
.take_while(|m| m.id.get() > message_id)
.collect::<Vec<_>>();
debug!(
"Filtered messages until {}. Left: {}",
@ -411,21 +282,19 @@ pub async fn purge(
response
.to_mut()
.edit(&ctx.serenity_context(), |e| {
e.set_embed(
.edit(
&ctx.serenity_context(),
EditMessage::new().embed(
serenity::CreateEmbed::default()
.title("Purge successful")
.field("Deleted messages", deleted_amount.to_string(), false)
.field("Action by", author.mention(), false)
.field("Action by", author.mention().to_string(), false)
.color(embed_color)
.thumbnail(&image)
.footer(|f| {
f.text("ReVanced");
f.icon_url(image)
})
.footer(CreateEmbedFooter::new("ReVanced").icon_url(image))
.clone(),
)
})
),
)
.await?;
Ok(())
}

View File

@ -3,8 +3,11 @@ use crate::utils::decancer::cure;
pub async fn guild_member_update(
ctx: &serenity::Context,
old_if_available: &Option<serenity::Member>,
new: &serenity::Member,
old_if_available: &Option<Member>,
new: &Option<Member>,
_: &GuildMemberUpdateEvent,
) {
cure(ctx, old_if_available, new).await;
if let Some(member) = new {
cure(ctx, old_if_available, member).await;
}
}

View File

@ -1,8 +1,8 @@
use chrono::{Duration, Utc};
use poise::serenity_prelude::{
ComponentType,
MessageComponentInteraction,
MessageComponentInteractionData,
ComponentInteraction,
ComponentInteractionData,
ComponentInteractionDataKind,
};
use super::*;
@ -10,11 +10,11 @@ use crate::utils;
pub async fn interaction_create(
ctx: &serenity::Context,
interaction: &serenity::Interaction,
) -> Result<(), crate::serenity::SerenityError> {
if let serenity::Interaction::MessageComponent(MessageComponentInteraction {
) -> Result<(), serenity::prelude::SerenityError> {
if let serenity::Interaction::Component(ComponentInteraction {
data:
MessageComponentInteractionData {
component_type: ComponentType::Button,
ComponentInteractionData {
kind: ComponentInteractionDataKind::Button,
custom_id,
..
},
@ -33,7 +33,7 @@ pub async fn handle_poll(
ctx: &serenity::Context,
interaction: &serenity::Interaction,
custom_id: &str,
) -> Result<(), crate::serenity::SerenityError> {
) -> Result<(), serenity::prelude::SerenityError> {
fn parse<T>(str: &str) -> T
where
<T as std::str::FromStr>::Err: std::fmt::Debug,

View File

@ -1,6 +1,14 @@
use std::sync::Arc;
use poise::serenity_prelude::{self as serenity, Mutex, RwLock, ShardManager, UserId};
use poise::serenity_prelude::prelude::RwLock;
use poise::serenity_prelude::{
self as serenity,
GuildMemberUpdateEvent,
Member,
Presence,
ShardManager,
UserId,
};
use tracing::log::error;
use crate::{Data, Error};
@ -15,7 +23,7 @@ pub struct Handler<T> {
options: poise::FrameworkOptions<T, Error>,
data: T,
bot_id: RwLock<Option<UserId>>,
shard_manager: RwLock<Option<Arc<Mutex<ShardManager>>>>,
shard_manager: RwLock<Option<Arc<ShardManager>>>,
}
// Custom handler to dispatch poise events
@ -29,18 +37,18 @@ impl<T: Send + Sync> Handler<T> {
}
}
pub async fn set_shard_manager(&self, shard_manager: Arc<Mutex<serenity::ShardManager>>) {
pub async fn set_shard_manager(&self, shard_manager: Arc<serenity::ShardManager>) {
*self.shard_manager.write().await = Some(shard_manager);
}
async fn dispatch_poise_event(&self, ctx: &serenity::Context, event: &poise::Event<'_>) {
async fn dispatch_poise_event(&self, event: &serenity::FullEvent) {
let framework_data = poise::FrameworkContext {
bot_id: self.bot_id.read().await.unwrap(),
options: &self.options,
user_data: &self.data,
shard_manager: &(*self.shard_manager.read().await).clone().unwrap(), /* Shard manager can be read between all poise events without locks */
};
poise::dispatch_event(framework_data, ctx, event).await;
poise::dispatch_event(framework_data, event).await;
}
}
@ -58,16 +66,18 @@ impl serenity::EventHandler for Handler<Arc<RwLock<Data>>> {
async fn guild_member_update(
&self,
ctx: serenity::Context,
old_if_available: Option<serenity::Member>,
new: serenity::Member,
old_if_available: Option<Member>,
new: Option<Member>,
event: GuildMemberUpdateEvent,
) {
guild_member_update::guild_member_update(&ctx, &old_if_available, &new).await;
guild_member_update::guild_member_update(&ctx, &old_if_available, &new, &event).await;
}
async fn message(&self, ctx: serenity::Context, new_message: serenity::Message) {
message_create::message_create(&ctx, &new_message).await;
self.dispatch_poise_event(&ctx, &poise::Event::Message {
self.dispatch_poise_event(&serenity::FullEvent::Message {
ctx,
new_message,
})
.await;
@ -80,7 +90,8 @@ impl serenity::EventHandler for Handler<Arc<RwLock<Data>>> {
new: Option<serenity::Message>,
event: serenity::MessageUpdateEvent,
) {
self.dispatch_poise_event(&ctx, &poise::Event::MessageUpdate {
self.dispatch_poise_event(&serenity::FullEvent::MessageUpdate {
ctx,
old_if_available,
new,
event,
@ -99,7 +110,8 @@ impl serenity::EventHandler for Handler<Arc<RwLock<Data>>> {
error!("Failed to handle interaction: {:?}.", e);
}
self.dispatch_poise_event(&ctx, &poise::Event::InteractionCreate {
self.dispatch_poise_event(&serenity::FullEvent::InteractionCreate {
ctx,
interaction,
})
.await;

View File

@ -0,0 +1,25 @@
use super::*;
use crate::model::application::Configuration;
use crate::utils::bot::get_data_lock;
use crate::utils::decancer::cure;
pub async fn presence_update(ctx: &serenity::Context, new_data: &Presence) {
let data = get_data_lock(ctx).await;
let configuration: &Configuration = &data.read().await.configuration;
if !configuration.general.cure_on_presence_update {
return;
}
cure(
ctx,
&None,
&new_data
.guild_id
.unwrap()
.member(&ctx.http, new_data.user.id)
.await
.unwrap(),
)
.await;
}

View File

@ -25,7 +25,9 @@ pub async fn load_muted_members(ctx: &serenity::Context, _: &serenity::Ready) {
while cursor.advance().await.unwrap() {
let current: Muted = cursor.deserialize_current().unwrap();
let Some(expires) = current.expires else { continue };
let Some(expires) = current.expires else {
continue;
};
let guild_id = current.guild_id.unwrap().parse::<u64>().unwrap();
let user_id = current.user_id.unwrap().parse::<u64>().unwrap();
@ -36,8 +38,8 @@ pub async fn load_muted_members(ctx: &serenity::Context, _: &serenity::Ready) {
queue_unmute_member(
ctx.clone(),
data.database.clone(),
serenity::GuildId(guild_id),
serenity::UserId(user_id),
guild_id.into(),
user_id.into(),
mute_role_id,
amount_left as u64, // i64 as u64 is handled properly here
),

View File

@ -6,7 +6,9 @@ use api::client::Api;
use commands::{configuration, misc, moderation};
use db::database::Database;
use events::Handler;
use poise::serenity_prelude::{self as serenity, RwLock, UserId};
use poise::serenity_prelude::prelude::{RwLock, TypeMapKey};
use poise::serenity_prelude::{CreateEmbed, UserId};
use poise::CreateReply;
use tokio::task::JoinHandle;
use tracing::{error, trace};
use utils::bot::load_configuration;
@ -24,7 +26,7 @@ mod utils;
type Error = Box<dyn std::error::Error + Send + Sync>;
type Context<'a> = poise::Context<'a, Arc<RwLock<Data>>, Error>;
impl serenity::TypeMapKey for Data {
impl TypeMapKey for Data {
type Value = Arc<RwLock<Data>>;
}
@ -54,8 +56,6 @@ async fn main() {
moderation::purge(),
moderation::ban(),
moderation::unban(),
moderation::lock(),
moderation::unlock(),
misc::reply(),
misc::poll(),
];
@ -68,7 +68,7 @@ async fn main() {
.users
.iter()
.cloned()
.map(UserId)
.map(UserId::from)
.collect::<Vec<UserId>>()
.into_iter()
.collect();
@ -116,7 +116,7 @@ async fn main() {
if !(administrators
.users
// Check if the user is an administrator
.contains(&member.user.id.0)
.contains(&member.user.id.get())
|| administrators
.roles
.iter()
@ -125,22 +125,23 @@ async fn main() {
member
.roles
.iter()
.any(|member_role| member_role.0 == role_id)
.any(|member_role| member_role.get() == role_id)
}))
{
if let Err(e) = ctx
.send(|m| {
m.ephemeral(true).embed(|e| {
e.title("Permission error")
.send(
CreateReply::new().ephemeral(true).embed(
CreateEmbed::new()
.title("Permission error")
.description(
"You do not have permission to use this command.",
)
.color(configuration.general.embed_color)
.thumbnail(member.user.avatar_url().unwrap_or_else(
|| member.user.default_avatar_url(),
))
})
})
)),
),
)
.await
{
error!("Error sending message: {:?}", e)
@ -152,9 +153,9 @@ async fn main() {
Ok(true)
})
}),
event_handler: |_ctx, event, _framework, _data| {
event_handler: |event, _framework, _data| {
Box::pin(async move {
tracing::trace!("{:?}", event.name());
tracing::trace!("{:?}", event.snake_case_name());
Ok(())
})
},
@ -163,12 +164,13 @@ async fn main() {
data.clone(), // Pass configuration as user data for the framework
));
let mut client = serenity::Client::builder(
let mut client = poise::serenity_prelude::Client::builder(
env::var("DISCORD_AUTHORIZATION_TOKEN")
.expect("DISCORD_AUTHORIZATION_TOKEN environment variable not set"),
serenity::GatewayIntents::non_privileged()
| serenity::GatewayIntents::MESSAGE_CONTENT
| serenity::GatewayIntents::GUILD_MEMBERS,
poise::serenity_prelude::GatewayIntents::non_privileged()
| poise::serenity_prelude::GatewayIntents::MESSAGE_CONTENT
| poise::serenity_prelude::GatewayIntents::GUILD_MEMBERS
| poise::serenity_prelude::GatewayIntents::GUILD_PRESENCES,
)
.event_handler_arc(handler.clone())
.await

View File

@ -66,6 +66,7 @@ pub struct General {
pub embed_color: i32,
pub mute: Mute,
pub logging_channel: u64,
pub cure_on_presence_update: bool,
}
#[derive(Default, Serialize, Deserialize)]

View File

@ -1,6 +1,6 @@
use std::sync::Arc;
use poise::serenity_prelude::{self as serenity, RwLock};
use poise::serenity_prelude::{self as serenity};
use crate::model::application::Configuration;
use crate::Data;
@ -10,7 +10,9 @@ pub fn load_configuration() -> Configuration {
}
// Share the lock reference between the threads in serenity framework
pub async fn get_data_lock(ctx: &serenity::Context) -> Arc<RwLock<Data>> {
pub async fn get_data_lock(
ctx: &serenity::Context,
) -> Arc<poise::serenity_prelude::prelude::RwLock<Data>> {
ctx.data.read().await.get::<Data>().unwrap().clone()
}
@ -21,7 +23,7 @@ pub async fn get_member(
) -> serenity::Result<Option<serenity::Member>> {
match guild_id.member(ctx, user_id).await {
Ok(member) => Ok(Some(member)),
Err(serenity::SerenityError::Http(err))
Err(serenity::prelude::SerenityError::Http(err))
if matches!(
err.status_code(),
Some(serenity::http::StatusCode::NOT_FOUND)

View File

@ -1,3 +1,4 @@
use poise::serenity_prelude::{CreateAttachment, CreateMessage, EditMessage};
use reqwest::Url;
use tracing::{debug, error, trace};
@ -63,26 +64,29 @@ pub async fn code_preview(ctx: &serenity::Context, new_message: &serenity::Messa
return; // Nothing to do
}
if let Err(err) = new_message
if let Err(err) = &new_message
.channel_id
.send_message(&ctx.http, |message| {
let mut message = message.reference_message(new_message);
.send_message(
&ctx.http,
{
let mut message = CreateMessage::new();
for preview in code_previews.iter() {
let language = match preview.code.language.as_ref() {
Some(language) => language,
None => "txt",
};
for preview in code_previews.iter() {
let language = match preview.code.language.as_ref() {
Some(language) => language,
None => "txt",
};
let name = format!("{}.{}", &preview.code.branch_or_sha, language);
let name = format!("{}.{}", &preview.code.branch_or_sha, language);
let content = preview.preview.as_ref().unwrap().as_bytes();
let content = preview.preview.as_ref().unwrap().as_bytes();
message = message.add_file((content, name.as_str()));
}
message = message.add_file(CreateAttachment::bytes(content, name.as_str()));
}
message
})
message
},
)
.await
{
error!(
@ -93,7 +97,11 @@ pub async fn code_preview(ctx: &serenity::Context, new_message: &serenity::Messa
return;
}
if let Err(err) = new_message.clone().suppress_embeds(&ctx.http).await {
if let Err(err) = new_message
.clone()
.edit(&ctx.http, EditMessage::new().suppress_embeds(true))
.await
{
error!("Failed to remove embeds. Error: {:?}", err);
}
}

View File

@ -1,5 +1,6 @@
extern crate decancer;
use poise::serenity_prelude::EditMember;
use tracing::{error, info, trace};
use super::*;
@ -17,7 +18,7 @@ pub async fn cure(
let name = member.display_name().to_string();
if let Some(old) = old_if_available {
if old.display_name().to_string() == name {
if *old.display_name() == name {
trace!(
"Skipping decancer for {} because their name hasn't changed",
member.user.tag()
@ -41,9 +42,11 @@ pub async fn cure(
match member
.guild_id
.edit_member(&ctx.http, member.user.id, |edit_member| {
edit_member.nickname(cured_name)
})
.edit_member(
&ctx.http,
member.user.id,
EditMember::default().nickname(cured_name),
)
.await
{
Ok(_) => info!("Cured user {}", member.user.tag()),

View File

@ -1,54 +1,48 @@
use chrono::Utc;
use poise::serenity_prelude::Message;
use poise::serenity_prelude::{CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, Message};
use poise::CreateReply;
pub fn clone_message<'a, 'b>(
message: &'a Message,
to_reply: &'b mut CreateReply<'a>,
) -> &'b mut CreateReply<'a> {
let mut reply = to_reply.content(message.content.as_str());
pub fn clone_message(message: &Message) -> CreateReply {
let mut reply = CreateReply::new().content(message.content.as_str());
if let Some(embed) = message.embeds.get(0) {
reply = reply.embed(|e| {
let mut new_embed = e;
if let Some(embed) = message.embeds.first() {
let mut new_embed = CreateEmbed::new();
if let Some(color) = embed.colour {
new_embed = new_embed.color(color);
}
if let Some(color) = embed.colour {
new_embed = new_embed.color(color);
}
new_embed = new_embed.timestamp(Utc::now().to_rfc3339());
new_embed = new_embed.timestamp(Utc::now());
if let Some(title) = &embed.title {
new_embed = new_embed.title(title);
}
if let Some(title) = &embed.title {
new_embed = new_embed.title(title);
}
if let Some(description) = &embed.description {
new_embed = new_embed.description(description);
}
if let Some(description) = &embed.description {
new_embed = new_embed.description(description);
}
if let Some(footer) = &embed.footer {
new_embed = new_embed.footer(|f| f.text(&footer.text));
}
if let Some(footer) = &embed.footer {
new_embed = new_embed.footer(CreateEmbedFooter::new(&footer.text));
}
if let Some(author) = &embed.author {
new_embed = new_embed.author(|a| a.name(&author.name));
}
if let Some(author) = &embed.author {
new_embed = new_embed.author(CreateEmbedAuthor::new(&author.name));
}
if let Some(image) = &embed.image {
new_embed = new_embed.image(image.url.as_str());
}
if let Some(image) = &embed.image {
new_embed = new_embed.image(image.url.as_str());
}
if let Some(thumbnail) = &embed.thumbnail {
new_embed = new_embed.thumbnail(thumbnail.url.as_str());
}
if let Some(thumbnail) = &embed.thumbnail {
new_embed = new_embed.thumbnail(thumbnail.url.as_str());
}
for field in &embed.fields {
new_embed =
new_embed.field(field.name.as_str(), field.value.as_str(), field.inline);
}
for field in &embed.fields {
new_embed = new_embed.field(field.name.as_str(), field.value.as_str(), field.inline);
}
new_embed
})
reply = reply.embed(new_embed);
}
reply

View File

@ -1,4 +1,12 @@
use chrono::{DateTime, Duration, NaiveDateTime, Utc};
use poise::serenity_prelude::{
CreateEmbed,
CreateEmbedAuthor,
CreateEmbedFooter,
CreateMessage,
EditThread,
GetMessages,
};
use regex::Regex;
use tracing::log::error;
@ -24,7 +32,7 @@ pub async fn handle_message_response(ctx: &serenity::Context, new_message: &sere
let member_roles = &member.roles;
let joined_at = member.joined_at.unwrap().unix_timestamp();
let must_joined_at = DateTime::<Utc>::from_utc(
let must_joined_at = DateTime::<Utc>::from_naive_utc_and_offset(
NaiveDateTime::from_timestamp_opt(joined_at, 0).unwrap(),
Utc,
);
@ -36,7 +44,7 @@ pub async fn handle_message_response(ctx: &serenity::Context, new_message: &sere
if !roles.iter().any(|&role_id| {
member_roles
.iter()
.any(|&member_role| role_id == member_role.0)
.any(|&member_role| role_id == member_role.get())
}) {
continue;
}
@ -44,7 +52,7 @@ pub async fn handle_message_response(ctx: &serenity::Context, new_message: &sere
if let Some(channels) = &includes.channels {
// check if the channel is whitelisted, if not, check if the channel is a thread, if it is check if the parent id is whitelisted
if !channels.contains(&new_message.channel_id.0) {
if !channels.contains(&new_message.channel_id.get()) {
if response.thread_options.is_some() {
if guild_message.is_none() {
guild_message = Some(
@ -57,8 +65,10 @@ pub async fn handle_message_response(ctx: &serenity::Context, new_message: &sere
);
};
let Some(parent_id) = guild_message.as_ref().unwrap().parent_id else { continue; };
if !channels.contains(&parent_id.0) {
let Some(parent_id) = guild_message.as_ref().unwrap().parent_id else {
continue;
};
if !channels.contains(&parent_id.get()) {
continue;
}
} else {
@ -80,7 +90,7 @@ pub async fn handle_message_response(ctx: &serenity::Context, new_message: &sere
if roles.iter().any(|&role_id| {
member_roles
.iter()
.any(|&member_role| role_id == member_role.0)
.any(|&member_role| role_id == member_role.get())
}) {
continue;
}
@ -120,32 +130,40 @@ pub async fn handle_message_response(ctx: &serenity::Context, new_message: &sere
}
if let Err(err) = channel_id
.send_message(&ctx.http, |m| {
if let Some(reference) = message_reference {
m.reference_message(reference);
} else {
m.reference_message(new_message);
}
.send_message(
&ctx.http,
{
let mut message = CreateMessage::default();
message = if let Some(reference) = message_reference {
message.reference_message(reference)
} else {
message.reference_message(new_message)
};
match &response.response.embed {
Some(embed) => m.embed(|e| {
e.title(&embed.title)
.description(&embed.description)
.color(embed.color)
.fields(embed.fields.iter().map(|field| {
(field.name.clone(), field.value.clone(), field.inline)
}))
.footer(|f| {
f.text(&embed.footer.text);
f.icon_url(&embed.footer.icon_url)
})
.thumbnail(&embed.thumbnail.url)
.image(&embed.image.url)
.author(|a| a.name(&embed.author.name).icon_url(&embed.author.icon_url))
}),
None => m.content(response.response.message.as_ref().unwrap()),
}
})
match &response.response.embed {
Some(embed) => message.embed(
CreateEmbed::new()
.title(&embed.title)
.description(&embed.description)
.color(embed.color)
.fields(embed.fields.iter().map(|field| {
(field.name.clone(), field.value.clone(), field.inline)
}))
.footer(
CreateEmbedFooter::new(&embed.footer.text)
.icon_url(&embed.footer.icon_url),
)
.thumbnail(&embed.thumbnail.url)
.image(&embed.image.url)
.author(
CreateEmbedAuthor::new(&embed.author.name)
.icon_url(&embed.author.icon_url),
),
),
None => message.content(response.response.message.as_ref().unwrap()),
}
},
)
.await
{
error!(
@ -154,7 +172,7 @@ pub async fn handle_message_response(ctx: &serenity::Context, new_message: &sere
err
);
} else if let Some(thread_options) = &response.thread_options {
let channel = channel_id
let mut channel = channel_id
.to_channel(&ctx.http)
.await
.unwrap()
@ -170,7 +188,7 @@ pub async fn handle_message_response(ctx: &serenity::Context, new_message: &sere
if thread_options.only_on_first_message
&& !channel_id
.messages(&ctx.http, |b| b.limit(1).before(new_message))
.messages(&ctx.http, GetMessages::new().limit(1).before(new_message))
.await
.unwrap()
.is_empty()
@ -179,10 +197,12 @@ pub async fn handle_message_response(ctx: &serenity::Context, new_message: &sere
}
if let Err(err) = channel
.edit_thread(&ctx.http, |e| {
e.locked(thread_options.lock_on_response)
.archived(thread_options.close_on_response)
})
.edit_thread(
&ctx.http,
EditThread::new()
.locked(thread_options.lock_on_response)
.archived(thread_options.close_on_response),
)
.await
{
error!(

View File

@ -1,8 +1,19 @@
use std::cmp;
use std::sync::{Arc, RwLock};
use std::sync::{Arc, Mutex};
use mongodb::options::FindOptions;
use poise::serenity_prelude::{ChannelId, GuildChannel, GuildId, Mentionable, User, UserId};
use poise::serenity_prelude::{
ChannelId,
CreateEmbed,
CreateEmbedFooter,
CreateMessage,
GuildId,
Mentionable,
User,
UserId,
};
use poise::CreateReply;
use serenity::prelude::SerenityError;
use tokio::task::JoinHandle;
use tracing::{debug, error, warn};
@ -11,7 +22,6 @@ use super::*;
use crate::db::database::Database;
use crate::db::model::Muted;
use crate::model::application::{Configuration, Mute};
use crate::serenity::SerenityError;
use crate::utils::bot::get_member;
use crate::{Context, Error};
@ -20,8 +30,6 @@ pub enum ModerationKind {
Unmute(User, User, Option<Error>), // User, Command author, Error
Ban(User, User, Option<String>, Option<SerenityError>), // User, Command author, Reason, Error
Unban(User, User, Option<SerenityError>), // User, Command author, Error
Lock(GuildChannel, User, Option<Error>), // Channel name, Command author, Error
Unlock(GuildChannel, User, Option<Error>), // Channel name, Command author, Error
}
pub enum BanKind {
Ban(User, Option<u8>, Option<String>), // User, Amount of days to delete messages, Reason
@ -48,7 +56,7 @@ pub async fn mute_on_join(ctx: &serenity::Context, new_member: &mut serenity::Me
if found {
debug!("Muted member {} rejoined the server", new_member.user.tag());
if new_member
.add_role(&ctx.http, RoleId(data.configuration.general.mute.role))
.add_role(&ctx.http, data.configuration.general.mute.role)
.await
.is_ok()
{
@ -85,7 +93,7 @@ pub fn queue_unmute_member(
.find_and_delete::<Muted>(
"muted",
Muted {
user_id: Some(user_id.0.to_string()),
user_id: Some(user_id.to_string()),
..Default::default()
}
.into(),
@ -123,18 +131,16 @@ pub async fn respond_moderation<'a>(
) -> Result<(), Error> {
let current_user = ctx.serenity_context().http.get_current_user().await?;
let send_ephemeral = RwLock::new(false);
let send_ephemeral = Arc::new(Mutex::new(false));
let create_embed = |f: &mut serenity::CreateEmbed| {
let mut moderated_user: Option<&User> = None;
let create_embed = || {
let f = CreateEmbed::new();
let result = match moderation {
ModerationKind::Mute(user, author, reason, expires, error) => {
moderated_user = Some(user);
let embed = match error {
Some(err) => {
*send_ephemeral.write().unwrap() = true;
*send_ephemeral.lock().unwrap() = true;
f.title(format!("Failed to mute {}", user.tag()))
.field("Exception", err.to_string(), false)
@ -158,40 +164,37 @@ pub async fn respond_moderation<'a>(
// add expiration date to embed if mute has a duration
if let Some(expire) = expires {
embed.field("Expires", expire, true);
embed.field("Expires", expire, true)
} else {
embed
}
embed
},
ModerationKind::Unmute(user, author, error) => {
moderated_user = Some(user);
match error {
Some(err) => {
*send_ephemeral.write().unwrap() = true;
ModerationKind::Unmute(user, author, error) => match error {
Some(err) => {
*send_ephemeral.lock().unwrap() = true;
f.title(format!("Failed to unmute {}", user.tag()))
.field("Exception", err.to_string(), false)
.field(
"Action",
format!(
"{} was unmuted by {} but failed",
user.mention(),
author.mention()
),
false,
)
},
None => f.title(format!("Unmuted {}", user.tag())).field(
"Action",
format!("{} was unmuted by {}", user.mention(), author.mention()),
false,
),
}
f.title(format!("Failed to unmute {}", user.tag()))
.field("Exception", err.to_string(), false)
.field(
"Action",
format!(
"{} was unmuted by {} but failed",
user.mention(),
author.mention()
),
false,
)
},
None => f.title(format!("Unmuted {}", user.tag())).field(
"Action",
format!("{} was unmuted by {}", user.mention(), author.mention()),
false,
),
},
ModerationKind::Ban(user, author, reason, error) => {
moderated_user = Some(user);
let f = match error {
Some(err) => {
*send_ephemeral.write().unwrap() = true;
*send_ephemeral.lock().unwrap() = true;
f.title(format!("Failed to ban {}", user.tag()))
.field("Exception", err.to_string(), false)
@ -217,164 +220,94 @@ pub async fn respond_moderation<'a>(
f
}
},
ModerationKind::Unban(user, author, error) => {
moderated_user = Some(user);
match error {
Some(err) => {
*send_ephemeral.write().unwrap() = true;
f.title(format!("Failed to unban {}", user.tag()))
.field("Exception", err.to_string(), false)
.field(
"Action",
format!(
"{} was unbanned by {} but failed",
user.mention(),
author.mention()
),
false,
)
},
None => f.title(format!("Unbanned {}", user.tag())).field(
"Action",
format!("{} was unbanned by {}", user.mention(), author.mention()),
false,
),
}
},
ModerationKind::Lock(channel, author, error) => match error {
ModerationKind::Unban(user, author, error) => match error {
Some(err) => {
*send_ephemeral.write().unwrap() = true;
*send_ephemeral.lock().unwrap() = true;
f.title(format!("Failed to lock {} ", channel.name()))
f.title(format!("Failed to unban {}", user.tag()))
.field("Exception", err.to_string(), false)
.field(
"Action",
format!(
"{} was locked by {} but failed",
channel.mention(),
"{} was unbanned by {} but failed",
user.mention(),
author.mention()
),
false,
)
},
None => f
.title(format!("Locked {}", channel.name()))
.description(
"Unlocking the channel will restore the original permission overwrites.",
)
.field(
"Action",
format!("{} was locked by {}", channel.mention(), author.mention()),
false,
),
},
ModerationKind::Unlock(channel, author, error) => match error {
Some(err) => {
*send_ephemeral.write().unwrap() = true;
f.title(format!("Failed to unlock {}", channel.name()))
.field("Exception", err.to_string(), false)
.field(
"Action",
format!(
"{} was unlocked by {} but failed",
channel.mention(),
author.mention()
),
false,
)
},
None => f
.title(format!("Unlocked {}", channel.name()))
.description("Restored original permission overwrites.")
.field(
"Action",
format!("{} was unlocked by {}", channel.mention(), author.mention()),
false,
),
None => f.title(format!("Unbanned {}", user.tag())).field(
"Action",
format!("{} was unbanned by {}", user.mention(), author.mention()),
false,
),
},
}
.color(configuration.general.embed_color);
let user = if let Some(user) = moderated_user {
user.face()
} else {
current_user.face()
};
let user = current_user.face();
result.thumbnail(&user).footer(|f| {
f.text("ReVanced");
f.icon_url(current_user.face())
});
result
.thumbnail(&user)
.footer(CreateEmbedFooter::new("ReVanced").icon_url(&user))
};
let send_ephemeral = *send_ephemeral.lock().unwrap();
let reply = ctx
.send(|reply| {
reply
.embed(|embed| {
create_embed(embed);
embed
})
.ephemeral(*send_ephemeral.read().unwrap())
})
.send(
CreateReply::new()
.embed(create_embed())
.ephemeral(send_ephemeral),
)
.await?;
let response = reply.message().await?;
ChannelId(configuration.general.logging_channel)
.send_message(&ctx.serenity_context().http, |reply| {
reply.embed(|embed| {
create_embed(embed);
embed.field(
"Reference",
format!(
"[Jump to message](https://discord.com/channels/{}/{}/{})",
ctx.guild_id().unwrap().0,
response.channel_id,
response.id
),
true,
)
})
})
ChannelId::from(configuration.general.logging_channel)
.send_message(
&ctx.serenity_context().http,
CreateMessage::new().embed(create_embed().field(
"Reference",
format!(
"[Jump to message](https://discord.com/channels/{}/{}/{})",
ctx.guild_id().unwrap(),
response.channel_id,
response.id
),
true,
)),
)
.await?;
Ok(())
}
pub async fn ban_moderation(ctx: &Context<'_>, kind: &BanKind) -> Option<SerenityError> {
let guild_id = ctx.guild_id().unwrap().0;
let guild_id = ctx.guild_id().unwrap();
let http = &ctx.serenity_context().http;
match kind {
BanKind::Ban(user, dmd, reason) => {
let reason = reason
.clone()
.or_else(|| Some("None specified".to_string()))
.unwrap();
let reason = reason.as_deref().or(Some("None specified"));
let ban_result = http
.ban_user(
guild_id,
user.id.0,
cmp::min(dmd.unwrap_or(0), 7),
reason.as_ref(),
)
.ban_user(guild_id, user.id, cmp::min(dmd.unwrap_or(0), 7), reason)
.await;
if let Err(err) = ban_result {
error!("Failed to ban user {}: {}", user.id.0, err);
error!("Failed to ban user {}: {}", user.id, err);
Some(err)
} else {
None
}
},
BanKind::Unban(user) => {
let unban_result = http.remove_ban(guild_id, user.id.0, None).await;
let unban_result = http.remove_ban(guild_id, user.id, None).await;
if let Err(err) = unban_result {
error!("Failed to unban user {}: {}", user.id.0, err);
error!("Failed to unban user {}: {}", user.id, err);
Some(err)
} else {
None
@ -391,7 +324,7 @@ pub async fn mute_moderation(
let mute_role_id = config.role;
let take = &config.take;
let is_currently_muted = member.roles.iter().any(|r| r.0 == mute_role_id);
let is_currently_muted = member.roles.iter().any(|r| r.get() == mute_role_id);
member
.add_role(&ctx.serenity_context().http, mute_role_id)
@ -401,7 +334,7 @@ pub async fn mute_moderation(
let removed_roles = member
.roles
.iter()
.filter(|r| take.contains(&r.0))
.filter(|r| take.contains(&r.get()))
.copied()
.collect::<Vec<_>>();
// take them from the member.

View File

@ -1,5 +1,13 @@
use base64::Engine;
use poise::serenity_prelude::{ButtonStyle, ReactionType, Timestamp};
use poise::serenity_prelude::{
CreateActionRow,
CreateButton,
CreateEmbed,
CreateEmbedFooter,
CreateInteractionResponseMessage,
ReactionType,
Timestamp,
};
use reqwest::StatusCode;
use tracing::log::{error, trace};
@ -11,7 +19,7 @@ pub async fn handle_poll(
interaction: &serenity::Interaction,
poll_id: u64,
min_join_date: Timestamp,
) -> Result<(), crate::serenity::SerenityError> {
) -> Result<(), serenity::prelude::SerenityError> {
trace!("Handling poll: {}.", poll_id);
let data = get_data_lock(ctx).await;
@ -52,40 +60,32 @@ pub async fn handle_poll(
.unwrap();
component
.create_interaction_response(&ctx.http, |r| {
r.interaction_response_data(|m| {
if let Ok(token) = result.as_deref() {
let url = format!("https://revanced.app/poll#{token}");
m.components(|c| {
c.create_action_row(|r| {
r.create_button(|b| {
b.label("Vote")
.emoji(ReactionType::Unicode("🗳️".to_string()))
.style(ButtonStyle::Link)
.url(&url)
})
})
})
} else {
m
}
.ephemeral(true)
.embed(|e| {
match result {
Ok(_) => e
.title("Cast your vote")
.description("You can now vote on the poll."),
Err(msg) => e.title("Error").description(msg),
}
.color(data.configuration.general.embed_color)
.thumbnail(&icon_url)
.footer(|f| {
f.text("ReVanced");
f.icon_url(&icon_url)
})
})
})
})
.create_response(
&ctx.http,
serenity::CreateInteractionResponse::Message(if let Ok(token) = result.as_deref() {
let url = format!("https://revanced.app/poll#{token}");
CreateInteractionResponseMessage::new().components(vec![CreateActionRow::Buttons(
vec![CreateButton::new_link(url)
.label("Vote")
.emoji(ReactionType::Unicode("🗳️".to_string()))],
)])
} else {
CreateInteractionResponseMessage::new()
.ephemeral(true)
.embed(
match result {
Ok(_) => CreateEmbed::new()
.title("Cast your vote")
.description("You can now vote on the poll."),
Err(msg) => CreateEmbed::new().title("Error").description(msg),
}
.color(data.configuration.general.embed_color)
.thumbnail(&icon_url)
.footer(CreateEmbedFooter::new("ReVanced").icon_url(&icon_url)),
)
}),
)
.await?;
Ok(())