mirror of
https://github.com/revanced/revanced-discord-bot.git
synced 2025-05-08 18:04:25 +02:00
feat: message-responders
& reload
command
This commit is contained in:
parent
216df9d1cf
commit
8fb0ab8835
27
Cargo.lock
generated
27
Cargo.lock
generated
@ -8,6 +8,15 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.56"
|
version = "0.1.56"
|
||||||
@ -606,6 +615,23 @@ dependencies = [
|
|||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.11"
|
version = "0.11.11"
|
||||||
@ -653,6 +679,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"log",
|
"log",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serenity",
|
"serenity",
|
||||||
|
@ -21,6 +21,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tokio = { version = "1.0", features = ["rt-multi-thread"] }
|
tokio = { version = "1.0", features = ["rt-multi-thread"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
regex = "1.0"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
|
||||||
[dependencies.serenity]
|
[dependencies.serenity]
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["channels", "message"],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"channels": {
|
"channels": {
|
||||||
"$ref": "#/$defs/channels",
|
"$ref": "#/$defs/channels",
|
||||||
|
@ -10,16 +10,16 @@ pub struct BotConfiguration {
|
|||||||
pub discord_authorization_token: String,
|
pub discord_authorization_token: String,
|
||||||
pub administrators: Administrators,
|
pub administrators: Administrators,
|
||||||
#[serde(rename = "thread-introductions")]
|
#[serde(rename = "thread-introductions")]
|
||||||
pub thread_introductions: Option<Vec<Introduction>>,
|
pub thread_introductions: Vec<Introduction>,
|
||||||
#[serde(rename = "message-responders")]
|
#[serde(rename = "message-responders")]
|
||||||
pub message_responders: Option<Vec<MessageResponder>>,
|
pub message_responders: Vec<MessageResponder>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize)]
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Administrators {
|
pub struct Administrators {
|
||||||
pub roles: Vec<u64>,
|
pub roles: Vec<u64>,
|
||||||
pub users: Option<Vec<u64>>,
|
pub users: Vec<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -32,26 +32,26 @@ pub struct Introduction {
|
|||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MessageResponder {
|
pub struct MessageResponder {
|
||||||
pub includes: Option<Includes>,
|
pub includes: Includes,
|
||||||
pub excludes: Option<Excludes>,
|
pub excludes: Excludes,
|
||||||
pub condition: Option<Condition>,
|
pub condition: Condition,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Includes {
|
pub struct Includes {
|
||||||
pub channels: Option<Vec<u64>>,
|
pub channels: Vec<u64>,
|
||||||
#[serde(rename = "match")]
|
#[serde(rename = "match")]
|
||||||
pub match_field: Option<Vec<String>>,
|
pub match_field: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Excludes {
|
pub struct Excludes {
|
||||||
pub roles: Option<Vec<u64>>,
|
pub roles: Vec<u64>,
|
||||||
#[serde(rename = "match")]
|
#[serde(rename = "match")]
|
||||||
pub match_field: Option<Vec<String>>,
|
pub match_field: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -64,7 +64,7 @@ pub struct Condition {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
#[serde(rename = "server-age")]
|
#[serde(rename = "server-age")]
|
||||||
pub server_age: Option<i16>,
|
pub server_age: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BotConfiguration {
|
impl BotConfiguration {
|
||||||
|
108
src/main.rs
108
src/main.rs
@ -1,14 +1,15 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use chrono::{DateTime, Duration, NaiveDateTime, Utc};
|
||||||
use configuration::BotConfiguration;
|
use configuration::BotConfiguration;
|
||||||
use log::{error, info, trace, LevelFilter};
|
use log::{error, info, trace, warn, LevelFilter};
|
||||||
use logger::logging::SimpleLogger;
|
use logger::logging::SimpleLogger;
|
||||||
|
use regex::Regex;
|
||||||
use serenity::client::{Context, EventHandler};
|
use serenity::client::{Context, EventHandler};
|
||||||
use serenity::model::application::command::Command;
|
use serenity::model::application::command::Command;
|
||||||
use serenity::model::channel::{GuildChannel, Message};
|
use serenity::model::channel::{GuildChannel, Message};
|
||||||
use serenity::model::gateway::Ready;
|
use serenity::model::gateway::Ready;
|
||||||
use serenity::model::prelude::interaction::{Interaction, InteractionResponseType};
|
use serenity::model::prelude::interaction::{Interaction, InteractionResponseType, MessageFlags};
|
||||||
use serenity::model::Timestamp;
|
|
||||||
use serenity::prelude::{GatewayIntents, RwLock, TypeMapKey};
|
use serenity::prelude::{GatewayIntents, RwLock, TypeMapKey};
|
||||||
use serenity::{async_trait, Client};
|
use serenity::{async_trait, Client};
|
||||||
mod configuration;
|
mod configuration;
|
||||||
@ -33,20 +34,44 @@ async fn get_configuration_lock(ctx: &Context) -> Arc<RwLock<BotConfiguration>>
|
|||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contains_match(strings: &Vec<String>, text: &String) -> bool {
|
||||||
|
strings.iter().any(|regex| Regex::new(regex).unwrap().is_match(&text))
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl EventHandler for Handler {
|
impl EventHandler for Handler {
|
||||||
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||||
trace!("Created an interaction: {:?}", interaction);
|
trace!("Created an interaction: {:?}", interaction);
|
||||||
|
|
||||||
|
let configuration_lock = get_configuration_lock(&ctx).await;
|
||||||
|
let mut configuration = configuration_lock.write().await;
|
||||||
|
|
||||||
if let Interaction::ApplicationCommand(command) = interaction {
|
if let Interaction::ApplicationCommand(command) = interaction {
|
||||||
let content = match command.data.name.as_str() {
|
let content = match command.data.name.as_str() {
|
||||||
"reload" => {
|
"reload" => {
|
||||||
|
let member = command.member.as_ref().unwrap();
|
||||||
|
let user_id = member.user.id.0;
|
||||||
|
|
||||||
|
let administrators = &configuration.administrators;
|
||||||
|
|
||||||
|
let mut permission_granted = false;
|
||||||
|
|
||||||
|
// check if the user is an administrator
|
||||||
|
if administrators.users.iter().any(|&id| user_id == id) {
|
||||||
|
permission_granted = true
|
||||||
|
}
|
||||||
|
// check if the user has an administrating role
|
||||||
|
if !permission_granted
|
||||||
|
&& administrators.roles.iter().any(|role_id| {
|
||||||
|
member.roles.iter().any(|member_role| member_role == role_id)
|
||||||
|
}) {
|
||||||
|
permission_granted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// if permission is granted, reload the configuration
|
||||||
|
if permission_granted {
|
||||||
trace!("{:?} reloading configuration.", command.user);
|
trace!("{:?} reloading configuration.", command.user);
|
||||||
|
|
||||||
let configuration_lock = get_configuration_lock(&ctx).await;
|
|
||||||
|
|
||||||
let mut configuration = configuration_lock.write().await;
|
|
||||||
|
|
||||||
let new_config =
|
let new_config =
|
||||||
BotConfiguration::load().expect("Could not load configuration.");
|
BotConfiguration::load().expect("Could not load configuration.");
|
||||||
|
|
||||||
@ -54,16 +79,23 @@ impl EventHandler for Handler {
|
|||||||
configuration.message_responders = new_config.message_responders;
|
configuration.message_responders = new_config.message_responders;
|
||||||
configuration.thread_introductions = new_config.thread_introductions;
|
configuration.thread_introductions = new_config.thread_introductions;
|
||||||
|
|
||||||
"Successfully reload configuration.".to_string()
|
"Successfully reloaded configuration.".to_string()
|
||||||
|
} else {
|
||||||
|
// else return an error message
|
||||||
|
"You do not have permission to use this command.".to_string()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => "Unknown command.".to_string(),
|
_ => "Unknown command.".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// send the response
|
||||||
if let Err(why) = command
|
if let Err(why) = command
|
||||||
.create_interaction_response(&ctx.http, |response| {
|
.create_interaction_response(&ctx.http, |response| {
|
||||||
response
|
response
|
||||||
.kind(InteractionResponseType::ChannelMessageWithSource)
|
.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
.interaction_response_data(|message| message.content(content))
|
.interaction_response_data(|message| {
|
||||||
|
message.content(content).flags(MessageFlags::EPHEMERAL)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@ -73,32 +105,48 @@ impl EventHandler for Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn message(&self, ctx: Context, msg: Message) {
|
async fn message(&self, ctx: Context, msg: Message) {
|
||||||
|
if msg.guild_id.is_none() || msg.author.bot {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
trace!("Received message: {}", msg.content);
|
trace!("Received message: {}", msg.content);
|
||||||
|
|
||||||
let configuration_lock = get_configuration_lock(&ctx).await;
|
if let Some(response) =
|
||||||
let configuration = configuration_lock.read().await;
|
get_configuration_lock(&ctx).await.read().await.message_responders.iter().find(
|
||||||
|
|&responder| {
|
||||||
|
// check if the message was sent in a channel that is included in the responder
|
||||||
|
responder.includes.channels.iter().any(|&channel_id| channel_id == msg.channel_id.0)
|
||||||
|
// check if the message was sent by a user that is not excluded from the responder
|
||||||
|
&& !responder.excludes.roles.iter().any(|&role_id| role_id == msg.author.id.0)
|
||||||
|
// check if the message does not match any of the excludes
|
||||||
|
&& !contains_match(&responder.excludes.match_field, &msg.content)
|
||||||
|
// check if the message matches any of the includes
|
||||||
|
&& contains_match(&responder.includes.match_field, &msg.content)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
let min_age = response.condition.user.server_age;
|
||||||
|
|
||||||
if let Some(message_responders) = &configuration.message_responders {
|
if min_age != 0 {
|
||||||
if let Some(responder) = message_responders.iter().find(|responder| {
|
let joined_at = ctx
|
||||||
responder.includes.iter().any(|include| {
|
|
||||||
include.channels.iter().any(|channel| todo!("Implement inclusion check"))
|
|
||||||
}) && responder.excludes.iter().all(|exclude| todo!("Implement exclusion check"))
|
|
||||||
}) {
|
|
||||||
if let Some(condition) = &responder.condition {
|
|
||||||
let join_date = ctx
|
|
||||||
.http
|
.http
|
||||||
.get_member(msg.guild_id.unwrap().0, msg.author.id.0)
|
.get_member(msg.guild_id.unwrap().0, msg.author.id.0)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.joined_at
|
.joined_at
|
||||||
.unwrap();
|
.unwrap()
|
||||||
|
.unix_timestamp();
|
||||||
|
|
||||||
let member_age = Timestamp::now().unix_timestamp() - join_date.unix_timestamp();
|
let must_joined_at =
|
||||||
|
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(joined_at, 0), Utc);
|
||||||
|
let but_joined_at = Utc::now() - Duration::days(min_age);
|
||||||
|
|
||||||
if let Some(age) = condition.user.server_age {
|
if must_joined_at <= but_joined_at {
|
||||||
todo!("Implement age check")
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg.reply_ping(&ctx.http, &response.message)
|
||||||
|
.await
|
||||||
|
.expect("Could not reply to message author.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,20 +156,14 @@ impl EventHandler for Handler {
|
|||||||
|
|
||||||
let configuration_lock = get_configuration_lock(&ctx).await;
|
let configuration_lock = get_configuration_lock(&ctx).await;
|
||||||
let configuration = configuration_lock.read().await;
|
let configuration = configuration_lock.read().await;
|
||||||
|
if let Some(introducer) = &configuration.thread_introductions.iter().find(|introducer| {
|
||||||
if let Some(introducers) = &configuration.thread_introductions {
|
introducer.channels.iter().any(|channel_id| *channel_id == thread.parent_id.unwrap().0)
|
||||||
if let Some(introducer) = introducers.iter().find(|introducer| {
|
|
||||||
introducer
|
|
||||||
.channels
|
|
||||||
.iter()
|
|
||||||
.any(|channel_id| *channel_id == thread.parent_id.unwrap().0)
|
|
||||||
}) {
|
}) {
|
||||||
if let Err(why) = thread.say(&ctx.http, &introducer.message).await {
|
if let Err(why) = thread.say(&ctx.http, &introducer.message).await {
|
||||||
error!("Error sending message: {:?}", why);
|
error!("Error sending message: {:?}", why);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async fn ready(&self, ctx: Context, ready: Ready) {
|
async fn ready(&self, ctx: Context, ready: Ready) {
|
||||||
info!("Connected as {}", ready.user.name);
|
info!("Connected as {}", ready.user.name);
|
||||||
@ -137,7 +179,7 @@ impl EventHandler for Handler {
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
log::set_logger(&LOGGER)
|
log::set_logger(&LOGGER)
|
||||||
.map(|()| log::set_max_level(LevelFilter::Info))
|
.map(|()| log::set_max_level(LevelFilter::Warn))
|
||||||
.expect("Could not set logger.");
|
.expect("Could not set logger.");
|
||||||
|
|
||||||
let configuration = BotConfiguration::load().expect("Failed to load configuration");
|
let configuration = BotConfiguration::load().expect("Failed to load configuration");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user