From 422bed5364e8ca2711e6190d8bca59e04f9c90e9 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 12 Jul 2022 15:08:26 +0200 Subject: [PATCH] feat: add `rustfmt` (#14) --- rustfmt.toml | 15 ++ src/logger.rs | 4 +- src/main.rs | 453 +++++++++++++++++++-------------------- src/model/application.rs | 165 +++++++------- 4 files changed, 314 insertions(+), 323 deletions(-) create mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..4b7d4d0 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,15 @@ +edition = "2021" +match_block_trailing_comma = true +newline_style = "Unix" +use_field_init_shorthand = true +use_try_shorthand = true + +hard_tabs = false +format_code_in_doc_comments = true +group_imports = "StdExternalCrate" +imports_granularity = "Module" +imports_layout = "HorizontalVertical" +match_arm_blocks = true +normalize_comments = true +overflow_delimited_expr = true +struct_lit_single_line = false diff --git a/src/logger.rs b/src/logger.rs index 93984d9..ab3b834 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,4 +1,4 @@ pub fn init() { - // TODO: log to file - tracing_subscriber::fmt::init(); + // TODO: log to file + tracing_subscriber::fmt::init(); } diff --git a/src/main.rs b/src/main.rs index 967e946..2672ad6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,259 +19,240 @@ mod model; struct BotConfiguration; impl TypeMapKey for BotConfiguration { - type Value = Arc>; + type Value = Arc>; } pub struct Handler; async fn get_configuration_lock(ctx: &Context) -> Arc> { - ctx.data - .read() - .await - .get::() - .expect("Expected Configuration in TypeMap.") - .clone() + ctx.data + .read() + .await + .get::() + .expect("Expected Configuration in TypeMap.") + .clone() } fn contains_match(regex: &[Regex], text: &str) -> bool { - regex.iter().any(|r| r.is_match(text)) + regex.iter().any(|r| r.is_match(text)) } fn load_configuration() -> Configuration { - Configuration::load().expect("Failed to load configuration") + Configuration::load().expect("Failed to load configuration") } #[async_trait] impl EventHandler for Handler { - async fn interaction_create(&self, ctx: Context, interaction: Interaction) { - debug!("Created an interaction: {:?}", interaction); - - if let Interaction::ApplicationCommand(command) = interaction { - let configuration_lock = get_configuration_lock(&ctx).await; - let mut configuration = configuration_lock.write().await; - - let administrators = &configuration.administrators; - let member = command.member.as_ref().unwrap(); - let user_id = member.user.id.0; - let mut stop_command = false; - 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 - } - - let content = if permission_granted { - match command.data.name.as_str() { - "reload" => { - debug!("{:?} reloaded the configuration.", command.user); - - let new_config = load_configuration(); - - configuration.administrators = new_config.administrators; - configuration.message_responses = new_config.message_responses; - configuration.thread_introductions = new_config.thread_introductions; - - "Successfully reloaded configuration.".to_string() - } - "stop" => { - debug!("{:?} stopped the bot.", command.user); - stop_command = true; - "Stopped the bot.".to_string() - } - _ => "Unknown command.".to_string(), - } - } else { - "You do not have permission to use this command.".to_string() - }; - - // send the response - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.content(content).flags(MessageFlags::EPHEMERAL) - }) - }) - .await - { - error!("Cannot respond to slash command: {}", why); - } - - if stop_command { - process::exit(0); - } - } - } - - async fn message(&self, ctx: Context, msg: Message) { - debug!("Received message: {}", msg.content); - if msg.guild_id.is_none() || msg.author.bot { - return; - } - - if let Some(message_response) = get_configuration_lock(&ctx) - .await - .read() - .await - .message_responses - .iter() - .find(|&response| { - // check if the message was sent in a channel that is included in the responder - response.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 - && !response.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(&response.excludes.match_field, &msg.content) - // check if the message matches any of the includes - && contains_match(&response.includes.match_field, &msg.content) - }) - { - let min_age = message_response.condition.user.server_age; - - if min_age != 0 { - let joined_at = ctx - .http - .get_member(msg.guild_id.unwrap().0, msg.author.id.0) - .await - .unwrap() - .joined_at - .unwrap() - .unix_timestamp(); - - let must_joined_at = - DateTime::::from_utc(NaiveDateTime::from_timestamp(joined_at, 0), Utc); - let but_joined_at = Utc::now() - Duration::days(min_age); - - if must_joined_at <= but_joined_at { - return; - } - - msg.channel_id - .send_message(&ctx.http, |m| { - m.reference_message(&msg); - match &message_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(message_response.response.message.as_ref().unwrap()), - } - }) - .await - .expect("Could not reply to message author."); - } - } - } - - async fn thread_create(&self, ctx: Context, thread: GuildChannel) { - if thread.member.is_some() { - debug!("Thread was joined. Block dispatch."); - return; - } - - debug!("Thread created: {:?}", thread); - - let configuration_lock = get_configuration_lock(&ctx).await; - let configuration = configuration_lock.read().await; - - if let Some(introducer) = &configuration - .thread_introductions - .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.response.message.as_ref().unwrap()) - .await - { - error!("Error sending message: {:?}", why); - } - } - } - - async fn ready(&self, ctx: Context, ready: Ready) { - info!("Connected as {}", ready.user.name); - - for (cmd, description) in [ - ("repload", "Reloads the configuration."), - ("stop", "Stop the Discord bot."), - ] { - Command::create_global_application_command(&ctx.http, |command| { - command.name(cmd).description(description) - }) - .await - .expect("Could not create command."); - } - } + async fn interaction_create(&self, ctx: Context, interaction: Interaction) { + debug!("Created an interaction: {:?}", interaction); + + if let Interaction::ApplicationCommand(command) = interaction { + let configuration_lock = get_configuration_lock(&ctx).await; + let mut configuration = configuration_lock.write().await; + + let administrators = &configuration.administrators; + let member = command.member.as_ref().unwrap(); + let user_id = member.user.id.0; + let mut stop_command = false; + 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 + } + + let content = if permission_granted { + match command.data.name.as_str() { + "reload" => { + debug!("{:?} reloaded the configuration.", command.user); + + let new_config = load_configuration(); + + configuration.administrators = new_config.administrators; + configuration.message_responses = new_config.message_responses; + configuration.thread_introductions = new_config.thread_introductions; + + "Successfully reloaded configuration.".to_string() + }, + "stop" => { + debug!("{:?} stopped the bot.", command.user); + stop_command = true; + "Stopped the bot.".to_string() + }, + _ => "Unknown command.".to_string(), + } + } else { + "You do not have permission to use this command.".to_string() + }; + + // send the response + if let Err(why) = command + .create_interaction_response(&ctx.http, |response| { + response + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|message| { + message.content(content).flags(MessageFlags::EPHEMERAL) + }) + }) + .await + { + error!("Cannot respond to slash command: {}", why); + } + + if stop_command { + process::exit(0); + } + } + } + + async fn message(&self, ctx: Context, msg: Message) { + debug!("Received message: {}", msg.content); + if msg.guild_id.is_none() || msg.author.bot { + return; + } + + if let Some(message_response) = + get_configuration_lock(&ctx).await.read().await.message_responses.iter().find( + |&response| { + // check if the message was sent in a channel that is included in the responder + response.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 + && !response.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(&response.excludes.match_field, &msg.content) + // check if the message matches any of the includes + && contains_match(&response.includes.match_field, &msg.content) + }, + ) { + let min_age = message_response.condition.user.server_age; + + if min_age != 0 { + let joined_at = ctx + .http + .get_member(msg.guild_id.unwrap().0, msg.author.id.0) + .await + .unwrap() + .joined_at + .unwrap() + .unix_timestamp(); + + let must_joined_at = + DateTime::::from_utc(NaiveDateTime::from_timestamp(joined_at, 0), Utc); + let but_joined_at = Utc::now() - Duration::days(min_age); + + if must_joined_at <= but_joined_at { + return; + } + + msg.channel_id + .send_message(&ctx.http, |m| { + m.reference_message(&msg); + match &message_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(message_response.response.message.as_ref().unwrap()), + } + }) + .await + .expect("Could not reply to message author."); + } + } + } + + async fn thread_create(&self, ctx: Context, thread: GuildChannel) { + if thread.member.is_some() { + debug!("Thread was joined. Block dispatch."); + return; + } + + debug!("Thread created: {:?}", thread); + + let configuration_lock = get_configuration_lock(&ctx).await; + let configuration = configuration_lock.read().await; + + if let Some(introducer) = &configuration.thread_introductions.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.response.message.as_ref().unwrap()).await + { + error!("Error sending message: {:?}", why); + } + } + } + + async fn ready(&self, ctx: Context, ready: Ready) { + info!("Connected as {}", ready.user.name); + + for (cmd, description) in + [("repload", "Reloads the configuration."), ("stop", "Stop the Discord bot.")] + { + Command::create_global_application_command(&ctx.http, |command| { + command.name(cmd).description(description) + }) + .await + .expect("Could not create command."); + } + } } #[tokio::main] async fn main() { - // Initialize the logging framework. - logger::init(); - - // Set up the configuration. - let configuration = load_configuration(); - - // Load environment variables from .env file - dotenv::dotenv().ok(); - - // Get the Discord authorization token. - let token = env::var("DISCORD_AUTHORIZATION_TOKEN") - .expect("Could not load Discord authorization token"); - if token.len() != 70 { - error!("Invalid Discord authorization token."); - process::exit(1); - } - - // Create the Discord bot client. - let mut client = Client::builder( - &token, - GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT, - ) - .event_handler(Handler) - .await - .expect("Failed to create client"); - - // Save the configuration. - client - .data - .write() - .await - .insert::(Arc::new(RwLock::new(configuration))); - - // Start the Discord bot. - client.start().await.expect("failed to start discord bot"); - - info!("Client started."); + // Initialize the logging framework. + logger::init(); + + // Set up the configuration. + let configuration = load_configuration(); + + // Load environment variables from .env file + dotenv::dotenv().ok(); + + // Get the Discord authorization token. + let token = env::var("DISCORD_AUTHORIZATION_TOKEN") + .expect("Could not load Discord authorization token"); + if token.len() != 70 { + error!("Invalid Discord authorization token."); + process::exit(1); + } + + // Create the Discord bot client. + let mut client = Client::builder( + &token, + GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT, + ) + .event_handler(Handler) + .await + .expect("Failed to create client"); + + // Save the configuration. + client.data.write().await.insert::(Arc::new(RwLock::new(configuration))); + + // Start the Discord bot. + client.start().await.expect("failed to start discord bot"); + + info!("Client started."); } diff --git a/src/model/application.rs b/src/model/application.rs index b100c4f..98cc43e 100644 --- a/src/model/application.rs +++ b/src/model/application.rs @@ -1,7 +1,7 @@ use std::{ - fs::{self, File}, - io::{Read, Result, Write}, - path::Path, + fs::{self, File}, + io::{Read, Result, Write}, + path::Path, }; use dirs::config_dir; @@ -10,147 +10,142 @@ use serde::{Deserialize, Serialize}; #[derive(Default, Serialize, Deserialize)] pub struct Configuration { - pub administrators: Administrators, - pub thread_introductions: Vec, - pub message_responses: Vec, + pub administrators: Administrators, + pub thread_introductions: Vec, + pub message_responses: Vec, } const CONFIG_PATH: &str = "configuration.json"; impl Configuration { - fn save(&self) -> Result<()> { - let sys_config_dir = config_dir().expect("find config dir"); - - fs::create_dir_all(format!( - "{}/revanced-discord-bot", - sys_config_dir.to_string_lossy() - )) - .expect("create config dir"); - - let mut file = File::create(CONFIG_PATH)?; - let json = serde_json::to_string_pretty(&self)?; - file.write_all(json.as_bytes())?; - Ok(()) - } - - pub fn load() -> Result { - let sys_config_dir = config_dir().expect("Can not find the configuration directory."); - let sys_config = format!( - "{}/revanced-discord-bot/{CONFIG_PATH}", - sys_config_dir.to_string_lossy() - ); - - // config file in current dir - let mut file = if Path::new(CONFIG_PATH).exists() { - File::open(CONFIG_PATH)? - } - // config file in system dir (on *nix: `~/.config/revanced-discord-bot/`) - else if Path::new(&sys_config).exists() { - File::open(sys_config)? - } - // create defalt config - else { - let default_config = Configuration::default(); - default_config.save()?; - - File::open(sys_config)? - }; - - let mut buf = String::new(); - file.read_to_string(&mut buf)?; - - Ok(serde_json::from_str(&buf)?) - } + fn save(&self) -> Result<()> { + let sys_config_dir = config_dir().expect("find config dir"); + + fs::create_dir_all(format!("{}/revanced-discord-bot", sys_config_dir.to_string_lossy())) + .expect("create config dir"); + + let mut file = File::create(CONFIG_PATH)?; + let json = serde_json::to_string_pretty(&self)?; + file.write_all(json.as_bytes())?; + Ok(()) + } + + pub fn load() -> Result { + let sys_config_dir = config_dir().expect("Can not find the configuration directory."); + let sys_config = + format!("{}/revanced-discord-bot/{CONFIG_PATH}", sys_config_dir.to_string_lossy()); + + // config file in current dir + let mut file = if Path::new(CONFIG_PATH).exists() { + File::open(CONFIG_PATH)? + } + // config file in system dir (on *nix: `~/.config/revanced-discord-bot/`) + else if Path::new(&sys_config).exists() { + File::open(sys_config)? + } + // create defalt config + else { + let default_config = Configuration::default(); + default_config.save()?; + + File::open(sys_config)? + }; + + let mut buf = String::new(); + file.read_to_string(&mut buf)?; + + Ok(serde_json::from_str(&buf)?) + } } #[derive(Default, Serialize, Deserialize)] pub struct Administrators { - pub roles: Vec, - pub users: Vec, + pub roles: Vec, + pub users: Vec, } #[derive(Serialize, Deserialize)] pub struct Introduction { - pub channels: Vec, - pub response: Response, + pub channels: Vec, + pub response: Response, } #[derive(Serialize, Deserialize)] pub struct MessageResponse { - pub includes: Includes, - pub excludes: Excludes, - pub condition: Condition, - pub response: Response, + pub includes: Includes, + pub excludes: Excludes, + pub condition: Condition, + pub response: Response, } #[derive(Serialize, Deserialize)] pub struct Response { - pub message: Option, - pub embed: Option, + pub message: Option, + pub embed: Option, } #[derive(Serialize, Deserialize)] pub struct Embed { - pub title: String, - pub description: String, - pub color: i32, - pub fields: Vec, - pub footer: Footer, - pub image: Image, - pub thumbnail: Thumbnail, - pub author: Author, + pub title: String, + pub description: String, + pub color: i32, + pub fields: Vec, + pub footer: Footer, + pub image: Image, + pub thumbnail: Thumbnail, + pub author: Author, } #[derive(Serialize, Deserialize)] pub struct Field { - pub name: String, - pub value: String, - pub inline: bool, + pub name: String, + pub value: String, + pub inline: bool, } #[derive(Serialize, Deserialize)] pub struct Footer { - pub text: String, - pub icon_url: String, + pub text: String, + pub icon_url: String, } #[derive(Serialize, Deserialize)] pub struct Image { - pub url: String, + pub url: String, } #[derive(Serialize, Deserialize)] pub struct Thumbnail { - pub url: String, + pub url: String, } #[derive(Serialize, Deserialize)] pub struct Author { - pub name: String, - pub icon_url: String, - pub url: String, + pub name: String, + pub icon_url: String, + pub url: String, } #[derive(Serialize, Deserialize)] pub struct Includes { - pub channels: Vec, - #[serde(rename = "match", with = "serde_regex")] - pub match_field: Vec, + pub channels: Vec, + #[serde(rename = "match", with = "serde_regex")] + pub match_field: Vec, } #[derive(Serialize, Deserialize)] pub struct Excludes { - pub roles: Vec, - #[serde(rename = "match", with = "serde_regex")] - pub match_field: Vec, + pub roles: Vec, + #[serde(rename = "match", with = "serde_regex")] + pub match_field: Vec, } #[derive(Serialize, Deserialize)] pub struct Condition { - pub user: User, + pub user: User, } #[derive(Serialize, Deserialize)] pub struct User { - pub server_age: i64, + pub server_age: i64, }