From 4d9e015d365be8347c7ca1a67a098fa2fddb2b9a Mon Sep 17 00:00:00 2001 From: ByteDice Date: Wed, 5 Mar 2025 19:20:36 +0100 Subject: [PATCH] added config file --- TODO.md | 1 + data/cfg.json | 6 ++++ data/cfg_default.json | 6 ++++ src/bk_week_cmds.rs | 55 ++++++-------------------------- src/cmds.rs | 31 +++++++++++++++++- src/data.rs | 52 ++++++++++++++++++++++++++++--- src/main.rs | 26 +++++++++------- src/python/bot.py | 14 +++++++-- src/python/cmds.py | 2 +- src/python/data.py | 64 +++++++++++++++++++++++++------------- src/python/main.py | 13 +++++--- src/python/posts.py | 2 +- src/python/py_websocket.py | 2 +- 13 files changed, 178 insertions(+), 96 deletions(-) create mode 100644 data/cfg.json create mode 100644 data/cfg_default.json diff --git a/TODO.md b/TODO.md index f99c945..4c6a8d1 100644 --- a/TODO.md +++ b/TODO.md @@ -9,6 +9,7 @@ + - [ ] Language files? - [ ] Allow updating the data autonomously and via manual commands. diff --git a/data/cfg.json b/data/cfg.json new file mode 100644 index 0000000..26044e0 --- /dev/null +++ b/data/cfg.json @@ -0,0 +1,6 @@ +{ + "bk_week": { + "fetch_limit": 100, + "subreddits": "bytedicetesting" + } +} \ No newline at end of file diff --git a/data/cfg_default.json b/data/cfg_default.json new file mode 100644 index 0000000..5f92ab3 --- /dev/null +++ b/data/cfg_default.json @@ -0,0 +1,6 @@ +{ + "bk_week": { + "subreddits": "bytedicetesting", + "fetch_limit": 100 + } +} \ No newline at end of file diff --git a/src/bk_week_cmds.rs b/src/bk_week_cmds.rs index 4c2c7d1..fd0214a 100644 --- a/src/bk_week_cmds.rs +++ b/src/bk_week_cmds.rs @@ -1,7 +1,7 @@ use crate::websocket::send_cmd_json; -use crate::{cmds, rs_println, websocket, Context, Data, Error, BK_WEEK}; +use crate::{cmds, rs_println, websocket, Context, Error, BK_WEEK}; use crate::messages::*; -use crate::data::{self, dc_bind_bk}; +use crate::data::{self, dc_bind_bk, get_mutex_data}; use std::collections::HashMap; use std::fs; @@ -81,7 +81,7 @@ pub async fn bk_week_get( { data::update_re_data(ctx.data()).await; - let reddit_data = get_reddit_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, &url).await? { send_embed_for_post(ctx, post, &url).await?; @@ -90,14 +90,6 @@ pub async fn bk_week_get( return Ok(()); } -pub async fn get_reddit_data(data: &Data) -> Result { - let data_lock = data.reddit_data.lock().await; - return match data_lock.as_ref() { - Some(data) => Ok(data.clone()), - None => Err("Reddit data is corrupted".into()), - }; -} - async fn get_post_from_data(ctx: Context<'_>, reddit_data: &Value, url: &str) -> Result, Error> { if let Some(bk_week) = reddit_data.get(BK_WEEK) { @@ -186,7 +178,7 @@ pub async fn bk_week_add( let shorturl = if shorturl_u.is_ok() { shorturl_u.unwrap() } else { url }; data::update_re_data(ctx.data()).await; - let reddit_data = get_reddit_data(ctx.data()).await.unwrap(); + let reddit_data = get_mutex_data(&ctx.data().reddit_data).await?; if let Some(bk_week) = reddit_data.get(BK_WEEK) { let a = approve.unwrap_or_else(|| false); @@ -293,7 +285,7 @@ pub async fn bk_week_approve( } data::update_re_data(ctx.data()).await; - let reddit_data = get_reddit_data(ctx.data()).await.unwrap(); + let reddit_data = get_mutex_data(&ctx.data().reddit_data).await?; approve_cmd(ctx, &url, &reddit_data, !disapprove.unwrap_or_else(|| false)).await; @@ -392,7 +384,7 @@ pub async fn bk_week_update( send_cmd_json("add_new_posts", Some(json!([max_age_secs]))).await; data::update_re_data(ctx.data()).await; - let r_data = get_reddit_data(ctx.data()).await.unwrap(); + let r_data = get_mutex_data(&ctx.data().reddit_data).await?; let c_id_u = get_c_id(ctx).await; @@ -464,8 +456,7 @@ async fn get_c_id(ctx: Context<'_>) -> Option { return None; } - let d_lock = ctx.data().discord_data.lock().await; - let d = d_lock.as_ref().unwrap(); + let d = get_mutex_data(&ctx.data().reddit_data).await.unwrap(); let c_id_u = d["servers"] [ctx.guild_id().unwrap().to_string()] @@ -675,7 +666,7 @@ pub async fn bk_week_vote( { data::update_re_data(ctx.data()).await; let uid = ctx.author().id.get(); - let re_data = get_reddit_data(ctx.data()).await.unwrap(); + let re_data = get_mutex_data(&ctx.data().reddit_data).await?; let post_data = re_data[BK_WEEK].clone(); let unw_vote = un_vote.unwrap_or_else(|| false); @@ -742,7 +733,7 @@ pub async fn bk_week_top( ) -> Result<(), Error> { let mut all: HashMap<&str, i32> = HashMap::new(); - let posts = &get_reddit_data(ctx.data()).await.unwrap()[BK_WEEK]; + let posts = &get_mutex_data(&ctx.data().reddit_data).await?[BK_WEEK]; let posts_u = posts.as_object().unwrap(); for (url, dat) in posts_u { @@ -789,32 +780,4 @@ fn smallest_n<'a>(map: &'a HashMap<&'a str, i32>, n: usize) -> Vec<(&'a str, i32 let mut vec: Vec<_> = map.iter().collect(); vec.sort_unstable_by(|a, b| a.1.cmp(b.1)); vec.into_iter().take(n).map(|(&k, &v)| (k, v)).collect() -} - - - - -#[poise::command( - slash_command, - prefix_command, - owners_only, - required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" -)] -/// Changes the subreddit(s) the bot patrols in. -pub async fn bk_cfg_sr( - ctx: Context<'_>, - sr: String -) -> Result<(), Error> -{ - let r = send_cmd_json("change_sr", Some(json!([sr]))).await; - - if r.is_some() { - if r.unwrap()["value"].as_bool().unwrap() { - send_msg(ctx, "Successfully updated subreddits!".to_string(), true, true).await; - return Ok(()); - } - } - - send_msg(ctx, "Failed to update subreddits: Failed-type response from Python.".to_string(), true, true).await; - return Ok(()); } \ No newline at end of file diff --git a/src/cmds.rs b/src/cmds.rs index d73e136..aa5f522 100644 --- a/src/cmds.rs +++ b/src/cmds.rs @@ -1,6 +1,6 @@ use std::process; -use crate::data::dc_add_server; +use crate::data::{dc_add_server, get_mutex_data}; use crate::websocket::send_cmd_json; use crate::{data, Context, Error}; use crate::messages::{edit_reply, send_embed, send_msg, Author, EmbedOptions, MANDATORY_MSG}; @@ -8,6 +8,7 @@ use crate::messages::{edit_reply, send_embed, send_msg, Author, EmbedOptions, MA use poise::serenity_prelude::{OnlineStatus, Timestamp}; use rand::{seq::IteratorRandom, Rng}; use regex::Regex; +use serde_json::json; #[poise::command( @@ -216,3 +217,31 @@ pub async fn add_server( return Ok(()); } + + +#[poise::command( + slash_command, + prefix_command, + owners_only, + required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL" +)] +/// Reloads the entire config file. +pub async fn reload_cfg( + ctx: Context<'_> +) -> Result<(), Error> +{ + + let d = get_mutex_data(&ctx.data().cfg).await?; + let d_str = serde_json::to_string(&d)?; + let r = send_cmd_json("update_cfg", Some(json!([d_str]))).await; // TODO: THIS + + if r.is_some() { + if r.unwrap()["value"].as_bool().unwrap() { + send_msg(ctx, "Successfully reloaded the configs!".to_string(), true, true).await; + return Ok(()); + } + } + + send_msg(ctx, "Failed to reload configs: Failed-type response from Python.".to_string(), true, true).await; + return Ok(()); +} \ No newline at end of file diff --git a/src/data.rs b/src/data.rs index dc564b0..cc39917 100644 --- a/src/data.rs +++ b/src/data.rs @@ -2,15 +2,18 @@ use std::{fs, io::Write}; use std::path::Path; use serde_json::{self, Value, json}; +use tokio::sync::Mutex; -use crate::{Data, BK_WEEK, rs_println}; +use crate::{Data, BK_WEEK, rs_println, Error}; use crate::websocket::send_cmd_json; -static DATA_PATH_DC: &str = "./data/discord_data.json"; -static PRESET_PATH_DC: &str = "./data/discord_data_preset.json"; -static DATA_PATH_RE: &str = "./data/reddit_data.json"; -static PRESET_PATH_RE: &str = "./data/reddit_data_preset.json"; +static DATA_PATH_DC: &str = "./data/discord_data.json"; +static PRESET_PATH_DC: &str = "./data/discord_data_preset.json"; +static DATA_PATH_RE: &str = "./data/reddit_data.json"; +static PRESET_PATH_RE: &str = "./data/reddit_data_preset.json"; +static DATA_PATH_CFG: &str = "./data/cfg.json"; +static PRESET_PATH_CFG: &str = "./data/cfg_default.json"; pub async fn read_dc_data(data: &Data, wipe: bool) { @@ -107,6 +110,35 @@ pub async fn write_re_data() { } +pub async fn read_cfg_data(data: &Data, wipe: bool) { + if !Path::new(DATA_PATH_CFG).exists() || wipe { + rs_println!( + "{} creating new from preset...", + if !wipe { "cfg.json not found," } else { "[WIPE] (cfg.json)" } + ); + generate_cfg_data(); + } + + let str_data = fs::read_to_string(DATA_PATH_CFG).unwrap(); + let json_data = serde_json::from_str(&str_data).unwrap(); + let mut cfg_data = data.cfg.lock().await; + *cfg_data = json_data; + + send_cmd_json("update_cfg", Some(json!([str_data]))).await; +} + + +fn generate_cfg_data() { + let preset_str = fs::read_to_string(PRESET_PATH_CFG).unwrap(); + let preset_json: Value = serde_json::from_str(&preset_str).unwrap(); + + let json_str = serde_json::to_string_pretty(&preset_json).unwrap(); + + let mut file = fs::File::create(DATA_PATH_CFG).unwrap(); + file.write_all(json_str.as_bytes()).unwrap(); +} + + pub async fn dc_add_server(data: &Data, server_id: u64) -> Result<(), ()> { let mut dc_data_lock = data.discord_data.lock().await; let dc_data = dc_data_lock.as_mut().unwrap(); @@ -154,4 +186,14 @@ pub async fn dc_contains_server(data: &Data, server_id: u64) -> bool { if servers.contains_key(&server_id.to_string()) { return true; } else { return false; } +} + + +#[allow(non_snake_case)] +pub async fn get_mutex_data(data: &Mutex>) -> Result { + let data_lock = data.lock().await; + return match data_lock.as_ref() { + Some(data) => Ok(data.clone()), + None => Err("Cannot get mutex data: The data is corrupted!".into()), + }; } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e2bbdda..5ea84e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ #![warn(unused_extern_crates)] +#![allow(clippy::needless_return)] mod cmds; mod bk_week_cmds; @@ -58,12 +59,13 @@ type Context<'a> = poise::Context<'a, Data, Error>; struct Data { - owners: Vec, + owners: Vec, ball_prompts: [Vec; 2], - reddit_data: Mutex>, + reddit_data: Mutex>, discord_data: Mutex>, - bk_mods: Vec, - args: Args + cfg: Mutex>, + bk_mods: Vec, + args: Args } @@ -159,14 +161,16 @@ async fn gen_data(args: Args, owners: Vec) -> Data { let data = Data { owners, ball_prompts: [ball_classic, ball_quirk], - bk_mods: mods_vec_u64, - reddit_data: None.into(), + bk_mods: mods_vec_u64, + reddit_data: None.into(), discord_data: None.into(), - args: args.clone() + cfg: None.into(), + args: args.clone() }; - data::read_dc_data(&data, args.clone().wipe).await; - data::read_re_data(&data, args.clone().wipe).await; + data::read_dc_data (&data, args.clone().wipe).await; + data::read_re_data (&data, args.clone().wipe).await; + data::read_cfg_data(&data, args.clone().wipe).await; return data; } @@ -212,8 +216,8 @@ async fn gen_bot(data: Data, args: Args) -> Client { bk_week_cmds::bk_week_top(), // bk_admin bk_week_cmds::bk_admin_bind(), - // bk_cfg - bk_week_cmds::bk_cfg_sr() + // cfg + cmds::reload_cfg() ], event_handler: events::event_handler, ..Default::default() diff --git a/src/python/bot.py b/src/python/bot.py index 7895a83..11b3c48 100644 --- a/src/python/bot.py +++ b/src/python/bot.py @@ -1,8 +1,13 @@ from io import TextIOWrapper import asyncpraw as praw import os - +from typing import Final from macros import * +import json + + +BK_WEEKLY: Final[str] = "bk_weekly_art_posts" +BK_WEEK: Final[str] = "bk_week" class Bot: @@ -12,6 +17,8 @@ class Bot: username: str = os.environ.get("ASSISTANT_R_NAME") password: str = os.environ.get("ASSISTANT_R_PASS") + fetch_limit = 0 + useragent: str =\ f"{username} by u/RandomPersonDotExe aka u/Byte_Dice"\ if r_id == "YmZjr4zLr2qtHdpQXtj0sBOOdJzrXQ"\ @@ -48,6 +55,7 @@ class Bot: return False - async def change_sr(self, new_sr) -> bool: - self.sr = await self.r.subreddit(new_sr) + async def update_cfg(self, new_cfg: dict) -> bool: + self.sr = await self.r.subreddit(new_cfg[BK_WEEK]["subreddits"]) + self.fetch_limit = new_cfg[BK_WEEK]["fetch_limit"] return True \ No newline at end of file diff --git a/src/python/cmds.py b/src/python/cmds.py index 896ab25..4e9b0f9 100644 --- a/src/python/cmds.py +++ b/src/python/cmds.py @@ -57,7 +57,7 @@ async def bk_week_add(mention: models.Comment, bot: botPy.Bot): short_url = mention.submission.shortlink r = "" - bd = bot.data[data.BK_WEEKLY] + bd = bot.data[botPy.BK_WEEKLY] # TODO: ask if the messages should be changed if short_url not in bd: posts.add_post_url(bot, short_url) diff --git a/src/python/data.py b/src/python/data.py index 606ea0a..5ce0367 100644 --- a/src/python/data.py +++ b/src/python/data.py @@ -1,13 +1,12 @@ import os import json -from typing import Final import time import bot as botPy from macros import * -BK_WEEKLY: Final[str] = "bk_weekly_art_posts" +DATA_PATH = os.path.abspath(os.path.join(os.path.join(os.getcwd(), "data"))) class PostData: @@ -67,9 +66,7 @@ class PostData: def read_data(bot: botPy.Bot) -> bool: - # Intentionally unreadable >:] - data_path = os.path.abspath(os.path.join(os.path.join(os.getcwd(), "data"))) - r_path = os.path.join(data_path, "reddit_data.json") + r_path = os.path.join(DATA_PATH, "reddit_data.json") if os.path.isfile(r_path): bot.data_f = open(r_path, "r+") @@ -79,11 +76,11 @@ def read_data(bot: botPy.Bot) -> bool: return False py_print("reddit_data.json not found, creating new from preset...") - with open(os.path.join(data_path, "reddit_data_preset.json", "r")) as f: + with open(os.path.join(DATA_PATH, "reddit_data_preset.json", "r")) as f: data_preset_json = json.load(f) - data_preset_json[BK_WEEKLY].pop("EXAMPLE VALUE", None) - data_preset_json[BK_WEEKLY].pop("EXAMPLE VALUE DELETED", None) + data_preset_json[botPy.BK_WEEKLY].pop("EXAMPLE VALUE", None) + data_preset_json[botPy.BK_WEEKLY].pop("EXAMPLE VALUE DELETED", None) with open(r_path, "w") as f: json.dump(data_preset_json, f, indent = 2) @@ -104,9 +101,32 @@ def write_data(bot: botPy.Bot) -> bool: return True +async def read_cfg(bot: botPy.Bot) -> bool: + r_path = os.path.join(DATA_PATH, "cfg.json") + + if os.path.isfile(r_path): + bot.data_f = open(r_path, "r+") + + else: + py_print("cfg.json not found, creating new from preset...") + with open(os.path.join(DATA_PATH, "cfg_default.json", "r")) as f: + data_preset_json = json.load(f) + + with open(r_path, "w") as f: + json.dump(data_preset_json, f, indent = 2) + + bot.data_f = open(r_path, "r+") + + data_str = bot.data_f.read() + json_data = json.loads(data_str) + await bot.update_cfg(json_data) + + return True + + def add_post_to_data(bot: botPy.Bot, new_data: PostData, bypass_conditions: bool = False) -> bool: if bypass_conditions: - bot.data[BK_WEEKLY][new_data.url] = new_data.to_json() + bot.data[botPy.BK_WEEKLY][new_data.url] = new_data.to_json() if bot.args["dev"]: py_print(f"Added post \"{new_data.url}\" (Conditions bypassed)") return True @@ -114,14 +134,14 @@ def add_post_to_data(bot: botPy.Bot, new_data: PostData, bypass_conditions: bool # not sure what this is for updated = False - if new_data.url not in bot.data[BK_WEEKLY] or updated: - bot.data[BK_WEEKLY][new_data.url] = new_data.to_json() + if new_data.url not in bot.data[botPy.BK_WEEKLY] or updated: + bot.data[botPy.BK_WEEKLY][new_data.url] = new_data.to_json() if bot.args["dev"]: py_print(f"Added post \"{new_data.url}\"") return True - if "removed" not in bot.data[BK_WEEKLY][new_data.url]: - updated = new_data.upvotes != bot.data[BK_WEEKLY][new_data.url]["post_data"] + if "removed" not in bot.data[botPy.BK_WEEKLY][new_data.url]: + updated = new_data.upvotes != bot.data[botPy.BK_WEEKLY][new_data.url]["post_data"] else: py_print(f"Failed to add post \"{new_data.url}\": Removed flag is True.") @@ -129,15 +149,15 @@ def add_post_to_data(bot: botPy.Bot, new_data: PostData, bypass_conditions: bool def set_approve_post(bot: botPy.Bot, approved: bool, url: str) -> bool: - if not hasattr(bot.data[BK_WEEKLY][url], "removed"): - bot.data[BK_WEEKLY][url]["approved"]["by_human"] = approved + if not hasattr(bot.data[botPy.BK_WEEKLY][url], "removed"): + bot.data[botPy.BK_WEEKLY][url]["approved"]["by_human"] = approved return True return False def remove_post(bot: botPy.Bot, url: str, removed_by: str = "UNKNOWN", reason: str = "None") -> bool: - weekly = bot.data[BK_WEEKLY] + weekly = bot.data[botPy.BK_WEEKLY] if url in weekly: weekly[url] = { @@ -153,7 +173,7 @@ def remove_post(bot: botPy.Bot, url: str, removed_by: str = "UNKNOWN", reason: s def remove_old_posts(bot: botPy.Bot, max_age: int) -> bool: now = int(time.time()) - weekly = bot.data[BK_WEEKLY] + weekly = bot.data[botPy.BK_WEEKLY] remove: list[str] = [] for url, post in weekly.items(): @@ -174,10 +194,10 @@ def set_vote_post( from_dc: bool = False, remove_vote: bool = False, ) -> bool: - if url not in bot.data[BK_WEEKLY]: + if url not in bot.data[botPy.BK_WEEKLY]: return False - votes = bot.data[BK_WEEKLY][url]["votes"] + votes = bot.data[botPy.BK_WEEKLY][url]["votes"] re_voters: set[str] = set(votes["voters_re"]) dc_voters: set[int] = set(votes["voters_dc"]) mod_voters: set[int] = set(votes["mod_voters"]) @@ -194,8 +214,8 @@ def set_vote_post( return False target_voters.add(user) - bot.data[BK_WEEKLY][url]["votes"]["voters_re"] = list(re_voters) - bot.data[BK_WEEKLY][url]["votes"]["voters_dc"] = list(dc_voters) - bot.data[BK_WEEKLY][url]["votes"]["mod_voters"] = list(mod_voters) + bot.data[botPy.BK_WEEKLY][url]["votes"]["voters_re"] = list(re_voters) + bot.data[botPy.BK_WEEKLY][url]["votes"]["voters_dc"] = list(dc_voters) + bot.data[botPy.BK_WEEKLY][url]["votes"]["mod_voters"] = list(mod_voters) return True diff --git a/src/python/main.py b/src/python/main.py index d5ac40f..9285bf8 100644 --- a/src/python/main.py +++ b/src/python/main.py @@ -26,17 +26,20 @@ async def main(): if bot.args["dev"]: py_print("ARGS:", str(bot.args)) - py_print("Reading data...") - dr = data.read_data(bot) + py_print("Reading config file...") + await data.read_cfg(bot) + + py_print("Reading Reddit data...") + rd = data.read_data(bot) data_retries = 0 - while not dr: + while not rd : data_retries += 1 time.sleep(1) py_print(f"Failed to read data: File doesn't exist yet. Retrying (#{data_retries}/5)...") - dr = data.read_data(bot) + rd = data.read_data(bot) - if data_retries == 5 and not dr: + if data_retries == 5 and not rd: raise Exception("Couldn't read reddit_data.json: File doesn't exist") py_print("Successfully read data!") diff --git a/src/python/posts.py b/src/python/posts.py index bbee15d..5677cd0 100644 --- a/src/python/posts.py +++ b/src/python/posts.py @@ -69,7 +69,7 @@ async def fetch_posts_with_flair(bot: botPy.Bot, flair_name: str) -> list[models posts: list[models.Submission] = [] # ~36 OG-art posts per week, round limit to 50, 75 or 100 - async for post in bot.sr.search(f"flair:\"{flair_name}\"", sort="new", limit=10): + async for post in bot.sr.search(f"flair:\"{flair_name}\"", sort="new", limit=bot.fetch_limit): posts.append(post) return posts diff --git a/src/python/py_websocket.py b/src/python/py_websocket.py index 01be77d..d72134c 100644 --- a/src/python/py_websocket.py +++ b/src/python/py_websocket.py @@ -82,7 +82,7 @@ async def json_to_func(v: dict, bot: botPy.Bot) -> dict: case "set_approve_post": r = data .set_approve_post (bot, *v["args"]) case "set_vote_post": r = data .set_vote_post (bot, *v["args"]) case "remove_old_posts": r = data .remove_old_posts (bot, *v["args"]) - case "change_sr": r = await bot .change_sr (*v["args"]) + case "update_cfg": r = await bot .update_cfg (*v["args"]) case "stop_praw": r = await bot .stop () case _: value_supported = False