From 9a87342dd2b12d98b79bcdd4696469973837cae6 Mon Sep 17 00:00:00 2001 From: ByteDice Date: Sun, 2 Mar 2025 01:44:53 +0100 Subject: [PATCH] command permissions, readme update, and command logic enhancements --- README.md | 38 +++++++++++++++++++++++++++----------- src/bk_week_cmds.rs | 37 ++++++++++++++++++++++--------------- src/cmds.rs | 20 ++++++++++---------- src/messages.rs | 18 +++++++++--------- 4 files changed, 68 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index c003b8d..af17665 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ An automation tool for Byte Dice. It's both a Discord and Reddit bot in one program. > [!CAUTION] -> This tool is not intended for public use outside of the official Byte Dice Assistant bots. Expect issues if you host this yourself. +> This tool is not intended for public use outside of the official *Byte Dice Assistant* bots. Expect issues if you host this yourself. # Open-source - Copyright @@ -31,21 +31,37 @@ You can install Python modules by running `$ pip install {module}` or `$ python ### Environment variables: | **Name** | **Description** | | --- | --- | -| `ASSISTANT_TOKEN` | The Discord bot token. | -| `ASSISTANT_TOKEN_TEST` | (Optional) A testing Discord bot token. This is only needed when the program is ran with `-t` or `--test`. | -| `ASSISTANT_DM_USER` | (Optional) Your Discord user id. The assistant will DM this user when *certain* errors occur. | -| `ASSISTANT_R_ID` | The id for the Reddit bot/account. | +| `ASSISTANT_TOKEN` | The Discord bot token. (Create a Discord bot [here](https://discord.com/developers/docs/intro)!) | +| `ASSISTANT_TOKEN_TEST` | (Optional) A testing Discord bot token. This is only needed when the program is run with `-t` or `--test`. | +| `ASSISTANT_DM_USER` | (Optional) Your Discord user ID. The assistant will DM this user when *certain* errors occur. | +| `ASSISTANT_R_ID` | The id for the Reddit bot/account. (Create a Reddit bot [here](https://www.reddit.com/prefs/apps)!) | | `ASSISTANT_R_TOKEN` | The token for the Reddit bot/account. | | `ASSISTANT_R_NAME` | The username of the Reddit bot/account. | | `ASSISTANT_R_PASS` | The password for the Reddit bot/account. | -| `ASSISTANT_OWNERS` | A list of user ids that "own" the bot. Separate each owner with a comma and NO spaces. | +| `ASSISTANT_OWNERS` | (OPTIONAL) A list of Discord user IDs that "own" the bot. Separate each ID with a single comma and **no** spaces. | ### Required permissions: +**These are automatically set if you use the [official invite link](https://discord.com/oauth2/authorize?client_id=1212127255795335208&permissions=84992&integration_type=0&scope=bot).** * Send Messages -* and... nothing else. +* Read Message History +* View Channels +* Embed Links ### How to run: -* Download the code (and extract if needed). -* Open a terminal and CD to the downloaded folder. -* Run `$ cargo run`. There are more options when running. You can view a list of those using `$ cargo run -- --help` or `$ cargo run -- -h`. - * If you want to only run the Python code, you can either run `$ cargo run -- --py`, or `$ python ./src/python/main.py`. The second option is recommended for better error output. \ No newline at end of file +* Download the code (and extract it if needed). +* Open a terminal. +* Set the environment variables. + * On Windows: + + * Run `$ setx VARIABLE_NAME "value"` in a terminal. + + * On Unix (Linux / Mac): + * Run `$ sudo nano /etc/environment` or `$ sudo vim /etc/environment` in the terminal. + * press `i` (only if you used VIM). + * write `VARIABLE_NAME="value"` + a new line for every variable. + * if nano: `ctrl + O` (and press enter) then `ctrl + X`. + * if VIM: press `esc` then write `:wq` (and press enter). +* Restart the terminal if you added/changed the environment variables. +* Run `$ cd path/to/extracted/folder`. +* Run `$ cargo run`. You can view a list of options by running `$ cargo run -- --help` or `$ cargo run -- -h`. + * If you only want to run the Python code, you can either run `$ cargo run -- --py`, or `$ python ./src/python/main.py`. The second option is recommended for better error output. \ No newline at end of file diff --git a/src/bk_week_cmds.rs b/src/bk_week_cmds.rs index e5dac22..c185cb5 100644 --- a/src/bk_week_cmds.rs +++ b/src/bk_week_cmds.rs @@ -43,7 +43,7 @@ async fn not_bk_mod_msg(ctx: Context<'_>) { #[poise::command( slash_command, prefix_command, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Shows helpful information on how to use the bk_week section of the bot. pub async fn bk_week_help( @@ -75,7 +75,7 @@ pub async fn bk_week_help( #[poise::command( slash_command, prefix_command, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL | EMBED_LINKS" )] /// Fetches the data of a single post, just for you. The data has to be within the database to work. pub async fn bk_week_get( @@ -172,7 +172,7 @@ async fn send_data_corrupted_message(ctx: Context<'_>, url: &str) { #[poise::command( slash_command, prefix_command, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Fetches a post from Reddit and adds it to the database. pub async fn bk_week_add( @@ -244,7 +244,7 @@ async fn send_updated_msg(ctx: Context<'_>, url: &str) { #[poise::command( slash_command, prefix_command, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Removes a post from the database. It will show who last removed it. pub async fn bk_week_remove( @@ -282,7 +282,7 @@ pub async fn bk_week_remove( #[poise::command( slash_command, prefix_command, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Approves a post in the database. Approving posts tells the bot that it's original. pub async fn bk_week_approve( @@ -338,7 +338,7 @@ async fn approve_cmd(ctx: Context<'_>, url: &str, reddit_data: &Value, approve: prefix_command, default_member_permissions = "ADMINISTRATOR", guild_only, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Sets the channel where the bot will dump all log info. It's recommended to only run this once. pub async fn bk_admin_bind( @@ -371,7 +371,7 @@ async fn send_server_not_in_data_msg(ctx: Context<'_>) { prefix_command, guild_only, guild_cooldown = 120, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL | READ_MESSAGE_HISTORY | EMBED_LINKS" )] /// Updates all logs pub async fn bk_week_update( @@ -418,7 +418,7 @@ pub async fn bk_week_update( // Adding new posts p_text = update_progress(ctx, progress.clone(), p_text.clone(), "✅\nAdding new posts...".to_string()).await; let weekly_art = r_data[BK_WEEK].as_object().unwrap(); - add_posts(http, c_id, weekly_art, &msgs_json).await; + add_posts(http, c_id, weekly_art, &msgs_json, max_age_secs).await; // Stop if only_add if only_add.unwrap_or_else(|| false) { @@ -552,7 +552,7 @@ async fn msgs_to_json<'a>(msgs: Vec, reddit_data: &'a Value, max_age: u let post_date = re_url["post_data"]["date_unix"].as_u64().unwrap_or_else(|| 0); // old - if now - post_date > max_age { + if now - post_date > max_age { if let Some(obj) = msgs_json["old"].as_object_mut() { obj.insert(url.clone(), json!(msg.id.get())); continue; @@ -600,13 +600,20 @@ async fn msgs_to_json<'a>(msgs: Vec, reddit_data: &'a Value, max_age: u } -async fn add_posts(http: &Http, c_id: ChannelId, r_data: &Map, msgs_json: &Value) { +async fn add_posts(http: &Http, c_id: ChannelId, r_data: &Map, msgs_json: &Value, max_age: u64) { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() as u64; + for url in r_data.keys() { - if ["no_change", "updated", "removed", "old"] + if ["no_change", "updated", "removed", "old", "duplicates"] .iter() .any(|key| msgs_json[key].as_object().unwrap().contains_key(url)) { continue; } - if msgs_json["duplicates"].as_object().unwrap().contains_key(url) { continue; } + + let post_date = r_data[url]["date_unix"].as_u64().unwrap(); + if now - post_date > max_age { continue; } if r_data[url].get("removed").is_some() { http_send_embed(http, c_id, embed_post_removed(&r_data[url], url, false)).await; @@ -661,7 +668,7 @@ async fn remove_dupes(http: &Http, c_id: ChannelId, msgs_json: &Value) { #[poise::command( slash_command, prefix_command, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Adds/removes a vote from a post. These votes are not tied to Reddit upvotes. pub async fn bk_week_vote( @@ -725,7 +732,7 @@ pub async fn bk_week_vote( #[poise::command( slash_command, prefix_command, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL | EMBED_LINKS" )] /// Gets the top N (up to 10) posts within a certain category, such as upvotes. (Sorted descending.) pub async fn bk_week_top( @@ -795,7 +802,7 @@ fn smallest_n<'a>(map: &'a HashMap<&'a str, i32>, n: usize) -> Vec<(&'a str, i32 slash_command, prefix_command, owners_only, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Changes the subreddit(s) the bot patrols in. pub async fn bk_cfg_sr( diff --git a/src/cmds.rs b/src/cmds.rs index b8a7453..d73e136 100644 --- a/src/cmds.rs +++ b/src/cmds.rs @@ -13,7 +13,7 @@ use regex::Regex; #[poise::command( slash_command, prefix_command, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Check if you have connection to the bot. pub async fn ping( @@ -31,7 +31,7 @@ pub async fn ping( slash_command, prefix_command, owners_only, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Stops the bot... if you're mighty enough! pub async fn stop( @@ -66,7 +66,7 @@ pub async fn stop( slash_command, prefix_command, owners_only, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL | EMBED_LINKS" )] /// Creates an embed. pub async fn embed( @@ -76,7 +76,7 @@ pub async fn embed( #[description = "Color of side strip."] color: Option, #[description = "A URL the title is bound to."] url: Option, #[description = "Timestamp at bottom (best to leave empty)."] timestamp: Option, - #[description = "Empheral (only visible to you)."] empheral: Option, + #[description = "Ephemeral (only visible to you)."] ephemeral: Option, #[description = "Shows \"used {Command}\" reply text."] reply: Option, #[description = "Text that appears above and outside of the embed."] message: Option, #[description = "A URL for a thumbnail image."] thumbnail: Option, @@ -93,7 +93,7 @@ pub async fn embed( col: color, url, ts: timestamp, - empheral: empheral.unwrap_or_else(|| false), + ephemeral: ephemeral.unwrap_or_else(|| false), message, thumbnail, author: if author.unwrap_or_else(|| false) { Some(Author { name: ctx.author().name.clone(), url: "".to_string(), icon_url: ctx.author().avatar_url().unwrap() }) } else { None } @@ -114,12 +114,12 @@ pub async fn embed( slash_command, prefix_command, owners_only, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Sends a message. pub async fn send( ctx: Context<'_>, - #[description = "The message to send (NO EMPHERAL)"] msg: String + #[description = "The message to send (NOT EPHEMERAL)"] msg: String ) -> Result<(), Error> { send_msg(ctx, msg.replace("\\n", "\n"), false, false).await; @@ -133,7 +133,7 @@ pub async fn send( slash_command, prefix_command, rename = "8_ball", - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Magic 8-ball. Ask a question, get an answer. pub async fn eight_ball( @@ -159,7 +159,7 @@ pub async fn eight_ball( #[poise::command( slash_command, prefix_command, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Convert a long reddit URL to a short one. The bot ONLY uses shortURLs when asking for one. pub async fn re_shorturl( @@ -198,7 +198,7 @@ pub fn to_shorturl(url: &str) -> Result { prefix_command, default_member_permissions = "ADMINISTRATOR", guild_only, - required_bot_permissions = "SEND_MESSAGES" + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" )] /// Add your server to my database so I can sell it! (/s), I only store some minimal data the bot needs. pub async fn add_server( diff --git a/src/messages.rs b/src/messages.rs index e7d2bfd..130a88e 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -21,7 +21,7 @@ pub struct EmbedOptions { pub col: Option, pub url: Option, pub ts: Option, - pub empheral: bool, + pub ephemeral: bool, pub message: Option, pub author: Option, pub thumbnail: Option @@ -34,7 +34,7 @@ impl Default for EmbedOptions { col: None, url: None, ts: None, - empheral: false, + ephemeral: false, message: None, author: None, thumbnail: None @@ -56,14 +56,14 @@ fn none_to_empty(string: Option) -> String { pub async fn send_msg( ctx: Context<'_>, t: String, - empheral: bool, + ephemeral: bool, reply: bool ) -> Option> { if reply { let r = CreateReply { content: Some(t), - ephemeral: Some(empheral), + ephemeral: Some(ephemeral), ..Default::default() }; @@ -104,7 +104,7 @@ pub async fn send_embed( let r = CreateReply { embeds: vec![embed], content: options.message, - ephemeral: Some(options.empheral), + ephemeral: Some(options.ephemeral), ..Default::default() }; @@ -200,7 +200,7 @@ pub async fn send_dm(msg: String, args: Args) { } -pub fn embed_post(post_data: &Value, url: &str, empheral: bool) -> EmbedOptions { +pub fn embed_post(post_data: &Value, url: &str, ephemeral: bool) -> EmbedOptions { let media_type = &post_data["post_data"]["media_type"]; let desc_str = format!( @@ -245,7 +245,7 @@ pub fn embed_post(post_data: &Value, url: &str, empheral: bool) -> EmbedOptions col: Some(DEFAULT_DC_COL), url: Some(url.to_string()), ts: Some(Timestamp::from_unix_timestamp(post_data["post_data"]["date_unix"].as_i64().unwrap()).unwrap()), - empheral, + ephemeral, thumbnail: media_urls.get(0) .and_then(|url| url.as_str().map(|s| s.to_string())) .or_else(|| None), @@ -254,7 +254,7 @@ pub fn embed_post(post_data: &Value, url: &str, empheral: bool) -> EmbedOptions } -pub fn embed_post_removed(post_data: &Value, url: &str, empheral: bool) -> EmbedOptions { +pub fn embed_post_removed(post_data: &Value, url: &str, ephemeral: bool) -> EmbedOptions { return EmbedOptions { title: Some("REMOVED!".to_string()), desc: format!( @@ -268,7 +268,7 @@ pub fn embed_post_removed(post_data: &Value, url: &str, empheral: bool) -> Embed col: Some(REMOVED_DC_COL), url: Some(url.to_string()), ts: Some(Timestamp::from_unix_timestamp(post_data["post_data"]["date_unix"].as_i64().unwrap()).unwrap()), - empheral, + ephemeral, ..Default::default() }; } \ No newline at end of file