diff --git a/data/defaults/cfg_default.toml b/data/defaults/cfg_default.toml index 2b3d08c..a0bcc72 100644 --- a/data/defaults/cfg_default.toml +++ b/data/defaults/cfg_default.toml @@ -9,9 +9,7 @@ status = "🎲 https://bytedice.net" [reddit] # Which subreddits the bot will scan when executing "re"-category commands. # Is automatically disabled when `disabled_categories` includes "re". -# Separate multiple subreddits with a "+", e.g "memes+askreddit". -# [REQUIRES RESTART] -subreddits = "bytedicetesting" +subreddits = ["bytedicetesting"] # How many posts the bot scans when running `/re_updatediscord`. fetch_limit = 100 diff --git a/src/cmds/help.rs b/src/cmds/help.rs index 7f00dfd..3431a60 100644 --- a/src/cmds/help.rs +++ b/src/cmds/help.rs @@ -164,7 +164,7 @@ fn separate_by_category(cmds: Vec<&Cmd>) -> Vec<(String, Vec<&Cmd>)> { for cmd in cmds { grouped .entry(cmd.category.clone().unwrap_or("No category".to_string())) - .or_insert_with(Vec::new).push(cmd); + .or_default().push(cmd); } return grouped.into_iter().collect(); diff --git a/src/cmds/reload_cfg.rs b/src/cmds/reload_cfg.rs index dcb0d43..4326022 100644 --- a/src/cmds/reload_cfg.rs +++ b/src/cmds/reload_cfg.rs @@ -14,7 +14,7 @@ pub async fn cmd( ctx: Context<'_> ) -> Result<(), Error> { - let r = read_cfg_data(&ctx.data(), false).await; + let r = read_cfg_data(ctx.data(), false).await; let d = get_toml_mutex(&ctx.data().cfg).await.unwrap(); if r.is_none() { return Ok(()); } diff --git a/src/cmds/stop.rs b/src/cmds/stop.rs index 3622602..e81d983 100644 --- a/src/cmds/stop.rs +++ b/src/cmds/stop.rs @@ -19,8 +19,9 @@ pub async fn cmd( #[description = "Type \"i want to stop the bot now\" to confirm."] confirmation: Option, ) -> Result<(), Error> { - let should_stop = ctx.data().args.dev - || confirmation.unwrap_or_default().to_lowercase() == "i want to stop the bot now"; + let stop_confirm = "i want to stop the bot now".replace(" ", ""); + let confirm_formatted = confirmation.unwrap_or_default().to_lowercase().replace(" ", ""); + let should_stop = ctx.data().args.dev || confirm_formatted == stop_confirm; if should_stop { let msg = send_msg(ctx, lang!("dc_msg_owner_data_save"), true, true).await.unwrap(); diff --git a/src/events.rs b/src/events.rs index 590f5d3..abe4aea 100644 --- a/src/events.rs +++ b/src/events.rs @@ -54,24 +54,24 @@ async fn handle_buttons(ctx: &serenity::Context, data: &Data, interaction: &Inte let url = i_embed.url.clone().unwrap(); return match component.data.custom_id.as_str() { - "approve_btn" => approve_btn(ctx, data, &component.member.as_ref().unwrap(), component, url, true).await, - "remove_btn" => remove_btn (ctx, data, &component.member.as_ref().unwrap(), component, url, true).await, - "unapprove_btn" => approve_btn(ctx, data, &component.member.as_ref().unwrap(), component, url, false).await, - "unremove_btn" => remove_btn (ctx, data, &component.member.as_ref().unwrap(), component, url, false).await, - "unvote_btn" => vote_btn (ctx, data, &component.member.as_ref().unwrap(), component, url, false).await, - "vote_btn" => vote_btn (ctx, data, &component.member.as_ref().unwrap(), component, url, true).await, + "approve_btn" => approve_btn(ctx, data, component.member.as_ref().unwrap(), component, url, true).await, + "remove_btn" => remove_btn (ctx, data, component.member.as_ref().unwrap(), component, url, true).await, + "unapprove_btn" => approve_btn(ctx, data, component.member.as_ref().unwrap(), component, url, false).await, + "unremove_btn" => remove_btn (ctx, data, component.member.as_ref().unwrap(), component, url, false).await, + "unvote_btn" => vote_btn (ctx, data, component.member.as_ref().unwrap(), component, url, false).await, + "vote_btn" => vote_btn (ctx, data, component.member.as_ref().unwrap(), component, url, true).await, _ => Err("Message button with that ID isn't handled.".into()) } } async fn update_embed(ctx: &serenity::Context, url: &str, new_data: &Value, c_id: &ChannelId, m_id: &MessageId) { - let e: EmbedOptions; let remove = new_data["removed"]["removed"].as_bool().unwrap(); - if remove { e = make_removed_embed(new_data, url, true); } - else { e = make_post_embed (new_data, url, true); } - - serenity_edit_msg_embed(ctx, &c_id, &m_id, e).await; + let e: EmbedOptions = + if remove { make_removed_embed(new_data, url, true) } + else { make_post_embed (new_data, url, true) }; + + serenity_edit_msg_embed(ctx, c_id, m_id, e).await; } @@ -106,14 +106,12 @@ async fn approve_btn(ctx: &serenity::Context, data: &Data, c_member: &Member, co async fn remove_btn(ctx: &serenity::Context, data: &Data, c_member: &Member, component: &ComponentInteraction, url: String, remove: bool) -> Result<(), Error> { if !is_bk_mod_serenity(ctx, data, c_member, component).await { return Ok(()); } - let r: Value; - - if remove { - r = send_cmd_json("remove_post_url", Some(json!([&url, &c_member.user.name, None::])), true).await.unwrap(); + let r: Value = if remove { + send_cmd_json("remove_post_url", Some(json!([&url, &c_member.user.name, None::])), true).await.unwrap() } else { - r = send_cmd_json("add_post_url", Some(json!([&url, false, true])), true).await.unwrap(); - } + send_cmd_json("add_post_url", Some(json!([&url, false, true])), true).await.unwrap() + }; let c_id = component.channel_id; let m_id = component.message.id; @@ -157,11 +155,11 @@ async fn vote_btn(ctx: &serenity::Context, data: &Data, c_member: &Member, compo serenity_send_msg(ctx, component, lang!("dc_msg_re_vote_remove_success"), true).await; } } - else { - if new_data["removed"]["removed"].as_bool().unwrap() { - serenity_send_msg(ctx, component, lang!("dc_msg_re_post_vote_removed_post"), true).await; - } - else if !vote { serenity_send_msg(ctx, component, lang!("dc_msg_re_vote_remove_havent"), true).await; } + else if new_data["removed"]["removed"].as_bool().unwrap() { + serenity_send_msg(ctx, component, lang!("dc_msg_re_post_vote_removed_post"), true).await; + } + else if !vote { + serenity_send_msg(ctx, component, lang!("dc_msg_re_vote_remove_havent"), true).await; } return Ok(()); diff --git a/src/macros.rs b/src/macros.rs index b7ec8cd..4fa57e1 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -66,7 +66,7 @@ macro_rules! warnln { macro_rules! lang { ($key:expr) => { { - use crate::{LANG, errln}; + use $crate::{LANG, errln}; let value = unsafe { LANG .as_ref() @@ -80,7 +80,7 @@ macro_rules! lang { } }; ($key:expr, $($arg:expr),*) => {{ - use crate::{LANG, errln}; + use $crate::{LANG, errln}; use formatx::formatx; let value = unsafe { diff --git a/src/main.rs b/src/main.rs index 588653b..7d62d56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,9 +69,9 @@ struct Args { wipe: bool, #[arg(short = 't', long, help = "Makes the program use the ASSISTANT_TOKEN_TEST env var instead of ASSISTANT_TOKEN. This env var should hold the token of a non-production bot.")] test: bool, - #[arg(long, help = "Adds annoying ping prints.")] + #[arg(long, help = "Adds annoying prints when the websockets send a ping. Why though?")] ping: bool, - #[arg(long, help = "Makes the program not use the schedules.")] + #[arg(long, help = "Makes the program not use the schedule system.")] nosched: bool } @@ -150,7 +150,7 @@ async fn main() { let python_args = args.clone(); let rust_args = args.clone(); - if !run_py { rs_println!("[IMPORTANT] You have disabled the \"re\" commands in the CFG. The app will not run the Python code nor the websockets to save resources!"); } + if !run_py { rs_println!("[IMPORTANT] You have disabled the \"re\" commands in the CFG. The app will not run the Python code and the websockets to save resources!"); } let rust = thread::spawn(move || { rt_rs.block_on(async { diff --git a/src/python/bot.py b/src/python/bot.py index cb34ff9..09d71c3 100644 --- a/src/python/bot.py +++ b/src/python/bot.py @@ -9,7 +9,8 @@ import toml RE_DATA_POSTS: Final[str] = "posts" CFG_DATA_RE: Final[str] = "reddit" - +# TODO: add wipe arg +# TODO: add test-bot arg class Bot: args: dict[str, Any] = {"NO_RUST": True, "dev": True, "py": True, "port": 2920} r_id: str | None = os.environ.get("ASSISTANT_R_ID") @@ -21,7 +22,7 @@ class Bot: useragent: str =\ f"{username} by u/RandomPersonDotExe aka u/Byte_Dice"\ - if r_id == "YmZjr4zLr2qtHdpQXtj0sBOOdJzrXQ"\ + if r_id == "YmZjr4zLr2qtHdpQXtj0sBOOdJzrXQ" or r_id == "Q-eBDGS8sFHlUCi9kpBepQ"\ else f"{username} (Original program by u/RandomPersonDotExe aka u/Byte_Dice)" if password is None: @@ -37,16 +38,13 @@ class Bot: password = self.password, user_agent = self.useragent ) - self.sr_list: list[str] = ["bytedicetesting"] + self.sr_list: list[str] = [] self.sr = None self.data_f: TextIOWrapper | None = None self.data: dict[str, Any] = {} self.flairs: list[str] = [] self.aliases: dict[str, list[str]] = {} - async def initialize(self): - self.sr = await self.r.subreddit("+".join(self.sr_list)) - async def set_args(self, args: dict[str, Any]): self.args = args @@ -67,5 +65,8 @@ class Bot: self.fetch_limit = new_cfg[CFG_DATA_RE]["fetch_limit"] self.flairs = new_cfg[CFG_DATA_RE]["search_flairs"] self.aliases = new_cfg[CFG_DATA_RE]["aliases"] + self.sr_list = new_cfg[CFG_DATA_RE]["subreddits"] + self.sr = await self.r.subreddit("+".join(self.sr_list)) init_lang(new_cfg["general"]["lang"]) + py_print("Successfully updated the configs!") return True \ No newline at end of file diff --git a/src/python/main.py b/src/python/main.py index 1504289..8a91ec6 100644 --- a/src/python/main.py +++ b/src/python/main.py @@ -7,17 +7,13 @@ import bot as botPy import py_data import py_websocket - async def main(): sys.stdout.reconfigure(encoding="utf-8") # type: ignore py_print("Creating Reddit bot...") bot = botPy.Bot() - await bot.initialize() - py_print(f"Successfully created Reddit bot: {await bot.r.user.me()}") - - # args is supposed to be undefined. + # args and lang_name are supposed to be undefined. # It gets defined in Rust. try: await bot.set_args(args) # type: ignore @@ -47,7 +43,9 @@ async def main(): if data_retries == 5 and not rd: raise Exception("Couldn't read re_data.json: File doesn't exist") - py_print("Successfully read data!") + py_print("Successfully read all data!") + + py_print(f"Successfully created Reddit bot: {await bot.r.user.me()}") if not bot.args["py"]: py_print("Connecting to local websocket...") diff --git a/src/python/posts.py b/src/python/posts.py index eb17555..b54568d 100644 --- a/src/python/posts.py +++ b/src/python/posts.py @@ -9,40 +9,33 @@ import bot as botPy from macros import * -async def add_new_posts(bot: botPy.Bot, max_age: int) -> bool: +async def add_new_posts(bot: botPy.Bot, max_age: int, max_results: int) -> bool: check_emoji = emoji.emojize(":check_mark_button:") cross_emoji = emoji.emojize(":cross_mark:") py_print("Fetching posts...") - posts = await fetch_posts_with_flair(bot, bot.flairs) + posts = await fetch_posts_with_flair(bot, bot.flairs, max_age, max_results) py_print("Evaluating posts...") added_posts = 0 without_media = 0 not_added = 0 - old_posts = 0 for post in posts: - media = has_media(post) + details = get_post_details(post) - media_urls = "\n ".join(media[3]) + media_urls = "\n ".join(details.media_urls) + media_check = check_emoji if details.media_type is not None else cross_emoji if bot.args["dev"]: py_print( - f"\n{post.title}", - f"\n {post.shortlink}" - f"\n {check_emoji if media[0] else cross_emoji} Media ({media[1]}) [{media[2]}]", + f"\n{details.title}", + f"\n {details.url}" + f"\n {media_check} Media ({details.media_type}) [{len(details.media_urls)}]", f"\n {media_urls}\n" ) - - details = get_post_details(post) - now = int(time.time()) - if now - details.date_unix > max_age and max_age > 0: - old_posts += 1 - continue - - if not media[0]: + if details.media_type is not None: without_media += 1 continue @@ -57,25 +50,32 @@ async def add_new_posts(bot: botPy.Bot, max_age: int) -> bool: py_print(f"Successfully fetched {len(posts)} posts.\n" + f" Out of which were {added_posts} added.\n" + f" {without_media} had no media, " + - f"{not_added} are removed or already existed, " + - f"and {old_posts} were older than the max age threshold.") + f" {not_added} are removed or already existed, ") py_data.write_data(bot) return True -async def fetch_posts_with_flair(bot: botPy.Bot, flair_names: list[str]) -> list[models.Submission]: +async def fetch_posts_with_flair( + bot: botPy.Bot, + flair_names: list[str], + max_age_secs: int, + max_results: int +) -> list[models.Submission]: posts: list[models.Submission] = [] flair_names_str = \ - f"flair:{flair_names[0]}" if len(flair_names) == 1\ - else " OR ".join(f"flair:{flair}" for flair in flair_names) + f"flair:{flair_names[0].replace(" ", "_")}" if len(flair_names) == 1\ + else " OR ".join(f"flair:{flair.replace(" ", "_")}" for flair in flair_names) if bot.sr is None: return [] - # ~36 OG-art posts per week, round limit to 50, 75 or 100 - async for post in bot.sr.search(f"{flair_names_str}", sort="new", limit=bot.fetch_limit): + now = int(time.time()) + + # ~20 OG-art posts per week, round limit to 50, 75 or 100 for 2 subreddits + async for post in bot.sr.search(f"{flair_names_str}", sort="new", limit=max_results): + if now - int(post.created_utc) > max_age_secs and max_age_secs > 0: continue posts.append(post) return posts diff --git a/src/re_cmds/add.rs b/src/re_cmds/add.rs index cc44f36..1066ea8 100644 --- a/src/re_cmds/add.rs +++ b/src/re_cmds/add.rs @@ -55,7 +55,7 @@ pub async fn cmd( if a { send_msg(ctx, lang!("dc_msg_re_also_approved"), true, true).await; } } - if let Some(post) = get_post_from_data(ctx, &reddit_data, &shorturl).await? { + if let Some(post) = get_post_from_data(ctx, &reddit_data, shorturl).await? { send_embed_for_post(ctx, post, &url).await?; } diff --git a/src/re_cmds/approve.rs b/src/re_cmds/approve.rs index 832992f..b30e79c 100644 --- a/src/re_cmds/approve.rs +++ b/src/re_cmds/approve.rs @@ -26,7 +26,7 @@ pub async fn cmd( data::update_re_data(ctx.data()).await; let reddit_data = get_mutex_data(&ctx.data().reddit_data).await?; - approve_cmd(ctx, &shorturl, &reddit_data, !disapprove.unwrap_or(false)).await; + approve_cmd(ctx, shorturl, &reddit_data, !disapprove.unwrap_or(false)).await; return Ok(()); } diff --git a/src/re_cmds/generic_fns.rs b/src/re_cmds/generic_fns.rs index a67d810..55939fb 100644 --- a/src/re_cmds/generic_fns.rs +++ b/src/re_cmds/generic_fns.rs @@ -13,7 +13,7 @@ pub async fn is_bk_mod_msg(ctx: Context<'_>) -> bool { if is_bk_mod(ctx.data().bk_mods.clone(), ctx.author().id.get()) { return true; } let sr = get_readable_subreddits(ctx.data()).await.unwrap(); - send_msg(ctx, lang!("dc_msg_re_permdeny_not_re_mod", sr), false, false).await; + send_msg(ctx, lang!("dc_msg_re_permdeny_not_re_mod", sr), true, true).await; return false } @@ -71,9 +71,16 @@ pub async fn send_embed_for_removed(ctx: Context<'_>, url: &str, post: &Value) { pub async fn get_readable_subreddits(data: &Data) -> Result { let d = get_toml_mutex(&data.cfg).await.unwrap(); - let sr = d["reddit"]["subreddits"].as_str().ok_or("Item of key \"subreddit\" is not a string type.\nTrace: `get_readable_subreddits -> let sr = ...`")?; - let split: Vec<&str> = sr.split("+").collect(); - let join = split.join(", r/"); + let sr = d["reddit"]["subreddits"].as_array().unwrap(); + let sr_str: Vec<&str> = sr + .iter() + .map(|v| v.as_str().unwrap()) + .collect(); + + let mut join = sr_str.join(", r/"); + + if join.len() != 0 { join = format!("r/{}", join); } + else { join = "[no subreddits assigned]".to_string(); } return Ok(join); } diff --git a/src/re_cmds/get.rs b/src/re_cmds/get.rs index c1d10e4..5391d80 100644 --- a/src/re_cmds/get.rs +++ b/src/re_cmds/get.rs @@ -24,8 +24,8 @@ pub async fn cmd( let reddit_data = get_mutex_data(&ctx.data().reddit_data).await?; - if let Some(post) = get_post_from_data(ctx, &reddit_data, &shorturl).await? { - send_embed_for_post(ctx, post, &shorturl).await?; + if let Some(post) = get_post_from_data(ctx, &reddit_data, shorturl).await? { + send_embed_for_post(ctx, post, shorturl).await?; } return Ok(()); diff --git a/src/re_cmds/remove.rs b/src/re_cmds/remove.rs index c4bec26..399d051 100644 --- a/src/re_cmds/remove.rs +++ b/src/re_cmds/remove.rs @@ -39,9 +39,9 @@ pub async fn cmd( data::update_re_data(ctx.data()).await; let reddit_data = get_mutex_data(&ctx.data().reddit_data).await?; - if let Some(post) = get_post_from_data(ctx, &reddit_data, &shorturl).await? { + if let Some(post) = get_post_from_data(ctx, &reddit_data, shorturl).await? { if post["removed"]["removed"].as_bool().unwrap() { - send_embed_for_removed(ctx, &shorturl, &post).await; + send_embed_for_removed(ctx, shorturl, &post).await; } } diff --git a/src/re_cmds/update.rs b/src/re_cmds/update.rs index 3b5750b..9549a72 100644 --- a/src/re_cmds/update.rs +++ b/src/re_cmds/update.rs @@ -3,7 +3,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use poise::{serenity_prelude::{ChannelId, EditMessage, GetMessages, Http, Message, MessageId, UserId}, ReplyHandle}; use serde_json::{json, Map, Value}; -use crate::{data::{self, get_mutex_data, DC_POSTS_CHANNEL_KEY}, lang, messages::{edit_reply, embed_from_options, make_post_embed, make_removed_embed, send_embed, send_msg, trim_post_json}, re_cmds::generic_fns::embed_to_json, rs_println, websocket::send_cmd_json, Context, Error, CFG_DATA_RE}; +use crate::{data::{self, get_mutex_data, get_toml_mutex, DC_POSTS_CHANNEL_KEY}, lang, messages::{edit_reply, embed_from_options, make_post_embed, make_removed_embed, send_embed, send_msg, trim_post_json}, re_cmds::generic_fns::embed_to_json, rs_println, websocket::send_cmd_json, Context, Error, CFG_DATA_RE}; #[poise::command( slash_command, @@ -17,12 +17,16 @@ use crate::{data::{self, get_mutex_data, DC_POSTS_CHANNEL_KEY}, lang, messages:: /// Updates the bound Discord channel with the bot's current Reddit data. pub async fn cmd( ctx: Context<'_>, - #[description = "Only adds new posts, leaves everything else unchanged."] + #[description = "Make this true to only add new posts and leave everything else unchanged."] only_add: Option, - #[description = "The max age of a post (in days). Any post older than this will be removed. (0 is infinite.)"] + #[description = "The max age of a post in days. Any post older than this will be removed. (0 is infinite)"] #[min = 0] #[max = 65535] - max_age: Option + max_age: Option, + #[description = "The max amount of posts to fetch (no value uses default value)."] + #[min = 1] + #[max = 100] + max_results: Option ) -> Result<(), Error> { let http = ctx.http(); @@ -35,7 +39,11 @@ pub async fn cmd( let max_age_u = max_age.unwrap_or(8); let max_age_secs = max_age_u as u64 * (60 * 60 * 24); - send_cmd_json("add_new_posts", Some(json!([max_age_secs])), true).await; + let max_results_toml = &get_toml_mutex(&ctx.data().cfg).await.unwrap(); + let max_results_pre = max_results_toml["reddit"]["fetch_limit"].as_integer().unwrap(); + let max_results_final = max_results.unwrap_or(max_results_pre as u16); + + send_cmd_json("add_new_posts", Some(json!([max_age_secs, max_results_final])), true).await; data::update_re_data(ctx.data()).await; let r_data = get_mutex_data(&ctx.data().reddit_data).await?; diff --git a/src/re_cmds/vote.rs b/src/re_cmds/vote.rs index 4e3b4dd..0fd9d6f 100644 --- a/src/re_cmds/vote.rs +++ b/src/re_cmds/vote.rs @@ -25,12 +25,12 @@ pub async fn cmd( let shorturl_u = to_shorturl(&url); let shorturl = &shorturl_u.unwrap_or(url.clone()); - if post_data.get(&shorturl).is_none() { + if post_data.get(shorturl).is_none() { send_msg(ctx, lang!("dc_msg_re_post_404"), false, false).await; return Ok(()); } if post_data[&shorturl]["removed"]["removed"].as_bool().unwrap() { - send_embed_for_removed(ctx, &shorturl, &post_data[&shorturl]).await; + send_embed_for_removed(ctx, shorturl, &post_data[&shorturl]).await; return Ok(()); }