From fa76443735508f0f38ed2df76555d52aa62f2329 Mon Sep 17 00:00:00 2001 From: ByteDice Date: Thu, 26 Jun 2025 11:16:17 +0200 Subject: [PATCH] added more config options & made python type-safe(r). --- data/defaults/cfg_default.toml | 15 ++++++++++- data/defaults/re_data_preset.json | 1 + data/lang/en.json | 4 +-- data/lang/gpt_fr.json | 6 ++--- src/cmds/eight_ball.rs | 7 ++--- src/cmds/reload_cfg.rs | 12 ++++----- src/data.rs | 8 +++--- src/main.rs | 22 +++++++-------- src/messages.rs | 1 + src/python/bot.py | 25 ++++++++--------- src/python/macros.py | 14 +++++----- src/python/main.py | 16 ++++++----- src/python/posts.py | 35 +++++++++++++----------- src/python/{cmds.py => py_cmds.py} | 2 +- src/python/{data.py => py_data.py} | 19 ++++++++----- src/python/py_websocket.py | 43 +++++++++++++++--------------- src/websocket.rs | 4 +-- 17 files changed, 132 insertions(+), 102 deletions(-) rename src/python/{cmds.py => py_cmds.py} (96%) rename src/python/{data.py => py_data.py} (93%) diff --git a/data/defaults/cfg_default.toml b/data/defaults/cfg_default.toml index 3ba1e6e..8be26b9 100644 --- a/data/defaults/cfg_default.toml +++ b/data/defaults/cfg_default.toml @@ -1,4 +1,8 @@ [general] +# The language of the bot. Available languages can be found in the data/lang folder. +# NOTE: Do not include the file extension of the language. For example, "en.json" is just "en". +lang = "en" + # The discord bots status text status = "🎲 https://bytedice.net" @@ -15,6 +19,12 @@ fetch_limit = 100 # Leave empty to search for everything search_flairs = ["Original Art"] +# Command aliases for the Reddit bot, since those aren't autocomplete-able. +# TODO: this +[reddit.aliases] +add_post = [] + + [commands] # Disabled selected command categories. All commands and their categories can be viewed in the README. # "admin" and "owner" categories are always enabled. @@ -23,4 +33,7 @@ disabled_categories = [ # "fun", # "help" # "re" -] \ No newline at end of file +] + +# The chance (between 0..1) for the `/8_ball` command to output a "quirky" answer. +eight_ball_quirky_chance = 0.2 \ No newline at end of file diff --git a/data/defaults/re_data_preset.json b/data/defaults/re_data_preset.json index 100d4b4..bc8ffcf 100644 --- a/data/defaults/re_data_preset.json +++ b/data/defaults/re_data_preset.json @@ -7,6 +7,7 @@ "reason": null }, "post_data": { + "subreddit": "bytedicetesting", "title": "I JUST BOUGHT THE CONTINENT OF NORTH AMERICA FOR A DOLLAR!", "upvotes": 69420, "date_unix": 1738614657, diff --git a/data/lang/en.json b/data/lang/en.json index a30af42..27e38b4 100644 --- a/data/lang/en.json +++ b/data/lang/en.json @@ -5,7 +5,7 @@ "dc_btn_unremove": "Restore", "dc_btn_unvote": "Un-vote", "dc_btn_vote": "Vote", - "dc_msg_8-ball_answer": "## You shook a magic 8-ball\nQ: {0}\nA: {1}", + "dc_msg_8-ball_answer": "## You shook a magic 8-ball...\n**Q:** {0}\n**A:** {1}", "dc_msg_add_to_data": "Added your server to my data! Thanks for letting me steal it! (/s)", "dc_msg_bound_channel": "Successfully bound channel ID `{0}` as the \"where all collected Reddit data gets dumped\" channel!", "dc_msg_cmd_404": "No command \"{0}\" found!\nHint: Try `/help` without any arguments or `/help `", @@ -14,7 +14,7 @@ "dc_msg_dm_python_err_socket": "Unknown internal Python error occurred: Websocket response error", "dc_msg_dm_python_err": "Unknown internal Python Error:\n```\n{0}\n```", "dc_msg_embed_default_embed_desc": "Default english embed description.", - "dc_msg_embed_re_post": "Spoilers and vote length anonymizer for fair review!\n## Post Data:\n**Post upvotes:** ||`{0:>6}`||\n**Moderator votes:** ||`{1:>6}`||\n**Media type:** `{2}`\n**URL:** ||<{3}>||\n\n## Listing Data:\n**Added by:** `{{ human: {4}, bot: {5} }}`\n**Approved by:** `{{ human: {6}, bot: [not implemented] }}`", + "dc_msg_embed_re_post": "Spoilers and vote length anonymizer for fair review!\n## Post Data:\n**Subreddit:** {0}\n**Post upvotes:** ||`{1:>6}`||\n**Moderator votes:** ||`{2:>6}`||\n**Media type:** `{3}`\n**URL:** ||<{4}>||\n\n## Listing Data:\n**Added by:** human: {5} | bot: {6}\n**Approved by:** human: {7} | bot: [not implemented]", "dc_msg_embed_re_removed": "## Removed by `{0}`\n**Reason:** {1}\n**URL**: ||<{2}>||", "dc_msg_err_trace": "Unknown error!\nError trace: {0}", "dc_msg_failed_shorturl_conversion": "Couldn't convert to shortURL: Invalid Reddit URL format.", diff --git a/data/lang/gpt_fr.json b/data/lang/gpt_fr.json index 361a651..4086afd 100644 --- a/data/lang/gpt_fr.json +++ b/data/lang/gpt_fr.json @@ -5,7 +5,7 @@ "dc_btn_unremove": "Restaurer", "dc_btn_unvote": "Annuler le vote", "dc_btn_vote": "Voter", - "dc_msg_8-ball_answer": "## Vous avez secoué une boule magique 8\nQ : {0}\nR : {1}", + "dc_msg_8-ball_answer": "## Vous avez secoué une boule magique 8...\n**Q :** {0}\n**R :** {1}", "dc_msg_add_to_data": "Serveur ajouté à mes données ! Merci de me laisser les voler ! (/s)", "dc_msg_bound_channel": "Canal avec l’identifiant `{0}` lié avec succès comme canal \"où toutes les données Reddit collectées sont envoyées\" !", "dc_msg_cmd_404": "Commande \"{0}\" introuvable !\nAstuce : Essayez `/help` sans arguments ou `/help `", @@ -14,7 +14,7 @@ "dc_msg_dm_python_err_socket": "Erreur Python interne inconnue : erreur de réponse WebSocket", "dc_msg_dm_python_err": "Erreur Python interne inconnue :\n```\n{0}\n```", "dc_msg_embed_default_embed_desc": "Description par défaut de l'encart en anglais.", - "dc_msg_embed_re_post": "Spoilers et anonymisation des votes pour une évaluation équitable !\n## Données du post :\n**Votes positifs du post :** ||`{0:>6}`||\n**Votes des modérateurs :** ||`{1:>6}`||\n**Type de média :** `{2}`\n**URL :** ||<{3}>||\n\n## Données de la liste :\n**Ajouté par :** `{{ humain : {4}, bot : {5} }}`\n**Approuvé par :** `{{ humain : {6}, bot : [non implémenté] }}`", + "dc_msg_embed_re_post": "Spoilers et anonymiseur de durée des votes pour une évaluation équitable !\n## Données du post :\n**Subreddit :** {0}\n**Votes positifs du post :** ||`{1:>6}`||\n**Votes des modérateurs :** ||`{2:>6}`||\n**Type de média :** `{3}`\n**URL :** ||<{4}>||\n\n## Données d'inscription :\n**Ajouté par :** humain : {5} | bot : {6}\n**Approuvé par :** humain : {7} | bot : [non implémenté]", "dc_msg_embed_re_removed": "## Supprimé par `{0}`\n**Raison :** {1}\n**URL** : ||<{2}>||", "dc_msg_err_trace": "Erreur inconnue !\nTrace de l’erreur : {0}", "dc_msg_failed_shorturl_conversion": "Échec de la conversion en shortURL : format d’URL Reddit invalide.", @@ -55,7 +55,7 @@ "dc_msg_update_removing_dupe": "{0}Suppression des posts en double...", "dc_msg_update_removing_old": "{0}Suppression des anciens posts (seuil : {1}j)...", "dc_msg_update_removing": "{0}Suppression des posts supprimés...", - "log_lang_load_success": "Fichier de langue anglais chargé avec succès !", + "log_lang_load_success": "Fichier de langue français chargé avec succès !", "none": "Aucun", "py_re_response_suffix": "^(Je ne suis pas une IA, juste un bot. Cette action a été effectuée automatiquement. Vous pouvez signaler des bugs et consulter mon code source [ici](https://github.com/ByteDice/ByteDiceAssistant) !)", "py_re_response_weekly_add": "Post ajouté avec succès aux soumissions hebdomadaires d'art ! Merci pour votre participation !", diff --git a/src/cmds/eight_ball.rs b/src/cmds/eight_ball.rs index 8100688..77d560f 100644 --- a/src/cmds/eight_ball.rs +++ b/src/cmds/eight_ball.rs @@ -1,6 +1,6 @@ use rand::{seq::IteratorRandom, Rng}; -use crate::{lang, messages::send_msg, Context, Error}; +use crate::{data::get_toml_mutex, lang, messages::send_msg, Context, Error}; #[poise::command( @@ -16,14 +16,15 @@ pub async fn cmd( #[description = "Question to ask."] question: String ) -> Result<(), Error> { - let is_quirky = rand::rng().random_bool(0.2); + let quirky_chance = get_toml_mutex(&ctx.data().cfg).await.unwrap()["commands"]["eight_ball_quirky_chance"].as_float().unwrap(); + let is_quirky = rand::rng().random_bool(quirky_chance.clamp(0.0, 1.0)); let list = &ctx.data().ball_prompts[if is_quirky { 1 } else { 0 }]; let rand_item = list.iter().choose(&mut rand::rng()); send_msg( ctx, lang!("dc_msg_8-ball_answer", question, rand_item.unwrap()), - true, + false, true ).await; diff --git a/src/cmds/reload_cfg.rs b/src/cmds/reload_cfg.rs index e9400f9..dcb0d43 100644 --- a/src/cmds/reload_cfg.rs +++ b/src/cmds/reload_cfg.rs @@ -1,6 +1,4 @@ -use serde_json::json; - -use crate::{data::{get_toml_mutex, read_cfg_data}, lang, messages::send_msg, websocket::send_cmd_json, Context, Error}; +use crate::{data::{self, get_toml_mutex, read_cfg_data}, lang, messages::send_msg, Context, Error}; #[poise::command( @@ -16,13 +14,15 @@ pub async fn cmd( ctx: Context<'_> ) -> Result<(), Error> { - 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(); - let d_str = toml::to_string(&d)?; - let r = send_cmd_json("update_cfg", Some(json!([d_str])), true).await; if r.is_none() { return Ok(()); } + let data_binding = get_toml_mutex(&ctx.data().cfg).await.unwrap(); + let lang_cfg = data_binding["general"]["lang"].as_str().unwrap(); + data::load_lang_data(lang_cfg.to_string()); + if r.unwrap()["value"].as_bool().unwrap() { send_msg( ctx, diff --git a/src/data.rs b/src/data.rs index ff51ca3..5ae6eda 100644 --- a/src/data.rs +++ b/src/data.rs @@ -95,7 +95,6 @@ fn generate_re_data() { if let Some(bk_week) = preset_json[CFG_DATA_RE].as_object_mut() { bk_week.remove("EXAMPLE VALUE"); - bk_week.remove("EXAMPLE VALUE DELETED"); } let json_str = serde_json::to_string_pretty(&preset_json).unwrap(); @@ -116,7 +115,7 @@ pub async fn write_re_data() { } -pub async fn read_cfg_data(data: &Data, wipe: bool) { +pub async fn read_cfg_data(data: &Data, wipe: bool) -> Option { if !Path::new(DATA_PATH_CFG).exists() || wipe { rs_println!( "{} creating new from preset...", @@ -128,9 +127,10 @@ pub async fn read_cfg_data(data: &Data, wipe: bool) { let str_data = fs::read_to_string(DATA_PATH_CFG).unwrap(); let json_data: toml::Value = str_data.parse().unwrap(); let mut cfg_data = data.cfg.lock().await; - *cfg_data = Some(json_data); + *cfg_data = Some(json_data.clone()); - send_cmd_json("update_cfg", Some(json!([str_data])), true).await; + let r = send_cmd_json("update_cfg", Some(json!([toml::to_string(&json_data).unwrap()])), true).await; + return r; } diff --git a/src/main.rs b/src/main.rs index da0efba..99712c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,12 +68,10 @@ 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 = "Removes the annoying ping prints.")] - noping: bool, + #[arg(long, help = "Adds annoying ping prints.")] + ping: bool, #[arg(long, help = "Makes the program not use the schedules.")] - nosched: bool, - #[arg(long, default_value = "en", help = "Which language file to use (Do not include file extensions)")] - lang: String + nosched: bool } @@ -104,12 +102,7 @@ pub static mut NOPING: bool = false; async fn main() { let args = ::parse(); let args_str = serde_json::to_string(&args).expect("Error serializing args to JSON"); - unsafe { NOPING = args.noping; } - - - rs_println!("Fetching language file..."); - data::load_lang_data(args.clone().lang); - rs_println!("[IMPORTANT] The below message is a test message, it should be written in the language you've selected\nTest message: {}", lang!("log_lang_load_success")); + unsafe { NOPING = !args.ping; } let own_env = std::env::var("ASSISTANT_OWNERS").unwrap_or("0".to_string()); let own_vec_str: Vec = own_env.split(",").map(String::from).collect(); @@ -118,8 +111,15 @@ async fn main() { .map(|s| s.parse::().expect("Failed to parse ASSISTANT_OWNERS. Invalid syntax.")) .collect(); + rs_println!("Generating and/or fetching data and config..."); let data = gen_data(args.clone(), own_vec_u64.clone()).await; + rs_println!("Fetching language file..."); + let data_binding = get_toml_mutex(&data.cfg).await.unwrap(); + let lang_cfg = data_binding["general"]["lang"].as_str().unwrap(); + data::load_lang_data(lang_cfg.to_string()); + rs_println!("[IMPORTANT] The below message is a test message, it should be written in the language you've selected\nTest message: {}", lang!("log_lang_load_success")); + if args.test { println!("----- USING TEST BOT -----"); } if args.dev { println!("----- DEV MODE ENABLED -----"); } if args.dev && args.wipe { println!("----- \"DON'T WORRY ABOUT IT\" MODE ENABLED -----"); } diff --git a/src/messages.rs b/src/messages.rs index 763fd88..5853300 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -219,6 +219,7 @@ pub fn make_post_embed(post_data: &Value, url: &str, ephemeral: bool) -> EmbedOp let desc_str = lang!( "dc_msg_embed_re_post", + post_data["post_data"]["subreddit"].as_str().unwrap(), post_data["post_data"]["upvotes"].as_i64().unwrap(), post_data["votes"]["mod_voters"].as_array().unwrap().len(), if !media_type.is_null() { media_type.as_str().unwrap() } else { "None" }, diff --git a/src/python/bot.py b/src/python/bot.py index 40ed8e1..19e48c0 100644 --- a/src/python/bot.py +++ b/src/python/bot.py @@ -1,7 +1,7 @@ from io import TextIOWrapper -import asyncpraw as praw +import asyncpraw as praw # type: ignore import os -from typing import Final +from typing import Final, Any from macros import * import toml @@ -11,11 +11,11 @@ CFG_DATA_RE: Final[str] = "reddit" class Bot: - args: dict = {"NO_RUST": True, "dev": True, "py": True, "port": 2920} - r_id: str = os.environ.get("ASSISTANT_R_ID") - secret: str = os.environ.get("ASSISTANT_R_TOKEN") - username: str = os.environ.get("ASSISTANT_R_NAME") - password: str = os.environ.get("ASSISTANT_R_PASS") + args: dict[str, Any] = {"NO_RUST": True, "dev": True, "py": True, "port": 2920} + r_id: str | None = os.environ.get("ASSISTANT_R_ID") + secret: str | None = os.environ.get("ASSISTANT_R_TOKEN") + username: str | None = os.environ.get("ASSISTANT_R_NAME") + password: str | None = os.environ.get("ASSISTANT_R_PASS") fetch_limit = 0 @@ -39,14 +39,14 @@ class Bot: ) self.sr_list: list[str] = ["bytedicetesting"] self.sr = None - self.data_f: TextIOWrapper = None - self.data: dict = {} + self.data_f: TextIOWrapper | None = None + self.data: dict[str, Any] = {} self.flairs: list[str] = [] async def initialize(self): self.sr = await self.r.subreddit("+".join(self.sr_list)) - async def set_args(self, args: dict): + async def set_args(self, args: dict[str, Any]): self.args = args async def stop(self) -> bool: @@ -59,12 +59,13 @@ class Bot: async def update_cfg_str(self, new_cfg: str) -> bool: json_cfg = toml.loads(new_cfg) - self.update_cfg(json_cfg) + await self.update_cfg(json_cfg) return True - async def update_cfg(self, new_cfg: dict) -> bool: + async def update_cfg(self, new_cfg: dict[str, Any]) -> bool: self.sr_list = new_cfg[CFG_DATA_RE]["subreddits"].split("+") self.sr = await self.r.subreddit("+".join(self.sr_list)) self.fetch_limit = new_cfg[CFG_DATA_RE]["fetch_limit"] self.flairs = new_cfg[CFG_DATA_RE]["search_flairs"] + init_lang(new_cfg["general"]["lang"]) return True \ No newline at end of file diff --git a/src/python/macros.py b/src/python/macros.py index 0248f40..61bc398 100644 --- a/src/python/macros.py +++ b/src/python/macros.py @@ -1,5 +1,6 @@ -import json import os +import json + from printColors import PrintColors @@ -8,14 +9,15 @@ G_LANG: dict[str, str] = {} DATA_PATH_LANG: str = "./data/lang/" -def py_print(*args): +def py_print(*args: str): print( PrintColors.FG.blue + "Py", "-", " ".join(args) + PrintColors.Special.reset ) -def py_error(*args): + +def py_error(*args: str): print( PrintColors.BG.red + "ERROR" + PrintColors.Special.reset, PrintColors.FG.blue + "Py", @@ -29,13 +31,13 @@ def lang(k: str) -> str: if G_LANG == {}: py_error("Language must be initialized before use!") t = G_LANG.get(k) - if k is None: py_error(f"Key not found in language \"{G_LANG_NAME}\": {k}") + if t is None: py_error(f"Key not found in language \"{G_LANG_NAME}\": {k}") return str(t) def init_lang(lang_name: str): global G_LANG, G_LANG_NAME - G_LANG_NAME = lang_name + G_LANG_NAME = lang_name # type: ignore full_path = f"{DATA_PATH_LANG}{lang_name}.json" @@ -50,4 +52,4 @@ def init_lang(lang_name: str): except json.JSONDecodeError as e: py_error(f"Failed to parse JSON for language \"{lang_name}\":\n{e}") - G_LANG = json_data \ No newline at end of file + G_LANG = json_data # type: ignore \ No newline at end of file diff --git a/src/python/main.py b/src/python/main.py index 8cf8ce3..f2a8592 100644 --- a/src/python/main.py +++ b/src/python/main.py @@ -4,12 +4,12 @@ import time from macros import * import bot as botPy -import data +import py_data import py_websocket async def main(): - sys.stdout.reconfigure(encoding="utf-8") + sys.stdout.reconfigure(encoding="utf-8") # type: ignore py_print("Creating Reddit bot...") bot = botPy.Bot() @@ -20,8 +20,10 @@ async def main(): # args is supposed to be undefined. # It gets defined in Rust. try: - await bot.set_args(args) - init_lang(lang_name) + await bot.set_args(args) # type: ignore + py_print("Fetching language file...") + init_lang(lang_name) # type: ignore + py_print(f"[IMPORTANT] The below message is a test message, it should be written in the language you've selected\nTest message: {lang('log_lang_load_success')}") except NameError: py_print("No command args or language name found from Rust. Don't worry though, we have backup in place.") init_lang("en") @@ -30,17 +32,17 @@ async def main(): py_print("ARGS:", str(bot.args)) py_print("Reading config file...") - await data.read_cfg(bot) + await py_data.read_cfg(bot) py_print("Reading Reddit data...") - rd = data.read_data(bot) + rd = py_data.read_data(bot) data_retries = 0 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)...") - rd = data.read_data(bot) + rd = py_data.read_data(bot) if data_retries == 5 and not rd: raise Exception("Couldn't read re_data.json: File doesn't exist") diff --git a/src/python/posts.py b/src/python/posts.py index f9651a7..176fd55 100644 --- a/src/python/posts.py +++ b/src/python/posts.py @@ -1,10 +1,10 @@ import emoji -from asyncpraw import models -import asyncprawcore as prawcore -import asyncpraw.exceptions as exc +from asyncpraw import models # type: ignore +import asyncprawcore as prawcore # type: ignore +import asyncpraw.exceptions as exc # type: ignore import time -import data +import py_data import bot as botPy from macros import * @@ -46,7 +46,7 @@ async def add_new_posts(bot: botPy.Bot, max_age: int) -> bool: without_media += 1 continue - post_added = data.add_post_to_data( + post_added = py_data.add_post_to_data( bot, details ) @@ -60,7 +60,7 @@ async def add_new_posts(bot: botPy.Bot, max_age: int) -> bool: f"{not_added} are removed or already existed, " + f"and {old_posts} were older than the max age threshold.") - data.write_data(bot) + py_data.write_data(bot) return True @@ -72,6 +72,8 @@ async def fetch_posts_with_flair(bot: botPy.Bot, flair_names: list[str]) -> list f"flair:{flair_names[0]}" if len(flair_names) == 1\ else " OR ".join(f"flair:{flair}" 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): posts.append(post) @@ -79,8 +81,8 @@ async def fetch_posts_with_flair(bot: botPy.Bot, flair_names: list[str]) -> list return posts -def has_media(post: models.Submission) -> tuple[bool, str, int, list[str]]: - media_type: str = None +def has_media(post: models.Submission) -> tuple[bool, str | None, int, list[str]]: + media_type: str | None = None media_count = 0 media_urls: list[str] = [] @@ -109,7 +111,7 @@ def has_media(post: models.Submission) -> tuple[bool, str, int, list[str]]: return (media_type != None, media_type, media_count, media_urls) -async def from_url(bot: botPy.Bot, url: str) -> tuple[bool, models.Submission]: +async def from_url(bot: botPy.Bot, url: str) -> tuple[bool, models.Submission | None]: try: post: models.Submission = await bot.r.submission(url=url) return True, post @@ -120,11 +122,12 @@ async def from_url(bot: botPy.Bot, url: str) -> tuple[bool, models.Submission]: return False, None -def get_post_details(post: models.Submission, added_by_h: bool = False) -> data.PostData: +def get_post_details(post: models.Submission, added_by_h: bool = False) -> py_data.PostData: media = has_media(post) - return data.PostData( + return py_data.PostData( post.shortlink, + post.subreddit, post.title, post.score, int(post.created_utc), @@ -135,12 +138,12 @@ def get_post_details(post: models.Submission, added_by_h: bool = False) -> data. ) -async def add_post_url(bot, url: str, approve: bool = False, added_by_h: bool = False) -> bool: +async def add_post_url(bot: botPy.Bot, url: str, approve: bool = False, added_by_h: bool = False) -> bool: result, post = await from_url(bot, url) - if not result: - return False - + if not result: return False + if post is None: return False + post_data = get_post_details(post, added_by_h) post_data.approved_by_human = approve - return data.add_post_to_data(bot, post_data, True) \ No newline at end of file + return py_data.add_post_to_data(bot, post_data, True) \ No newline at end of file diff --git a/src/python/cmds.py b/src/python/py_cmds.py similarity index 96% rename from src/python/cmds.py rename to src/python/py_cmds.py index 6727792..4a0df57 100644 --- a/src/python/cmds.py +++ b/src/python/py_cmds.py @@ -1,4 +1,4 @@ -import asyncpraw.models as models +import asyncpraw.models as models # type: ignore from macros import * import bot as botPy diff --git a/src/python/data.py b/src/python/py_data.py similarity index 93% rename from src/python/data.py rename to src/python/py_data.py index 19767ab..3bbe78d 100644 --- a/src/python/data.py +++ b/src/python/py_data.py @@ -2,6 +2,7 @@ import os import toml import json import time +from typing import Any import bot as botPy from macros import * @@ -17,10 +18,11 @@ class PostData: def __init__( self, url: str, + subreddit: str, title: str, upvotes: int, date_unix: int, - media_type: str, + media_type: str | None, media_urls: list[str], removed: bool = False, removed_by: str | None = None, @@ -33,6 +35,7 @@ class PostData: approved_by_human: bool = False, approved_by_ris: bool = False ): + self.subreddit = subreddit self.removed = removed self.removed_by = removed_by self.removed_reason = removed_reason @@ -50,7 +53,7 @@ class PostData: self.approved_by_human = approved_by_human self.approved_by_ris = approved_by_ris - def to_json(self): + def to_json(self) -> dict[str, Any]: return { "removed": { "removed": self.removed, @@ -58,6 +61,7 @@ class PostData: "reason": self.removed_reason }, "post_data": { + "subreddit": self.subreddit, "title": self.title, "upvotes": self.upvotes, "date_unix": self.date_unix, @@ -92,10 +96,9 @@ def read_data(bot: botPy.Bot) -> bool: py_print("re_data.json not found, creating new from preset...") with open(os.path.join(DEFAULT_PATH, "re_data_preset.json"), "r") as f: - data_preset_json = json.load(f) + data_preset_json: dict[str, Any] = json.load(f) data_preset_json[botPy.RE_DATA_POSTS].pop("EXAMPLE VALUE", None) - data_preset_json[botPy.RE_DATA_POSTS].pop("EXAMPLE VALUE DELETED", None) with open(r_path, "w") as f: json.dump(data_preset_json, f, indent = 2) @@ -110,6 +113,8 @@ def read_data(bot: botPy.Bot) -> bool: def write_data(bot: botPy.Bot) -> bool: + if bot.data_f is None: return False + bot.data_f.seek(0) json.dump(bot.data, bot.data_f, indent=2) bot.data_f.truncate() @@ -128,7 +133,7 @@ async def read_cfg(bot: botPy.Bot) -> bool: data_preset_json = toml.load(f) with open(r_path, "w") as f: - toml.dump(data_preset_json, f, indent = 2) + toml.dump(data_preset_json, f, indent = 2) # type: ignore bot.data_f = open(r_path, "r+") @@ -224,12 +229,12 @@ def set_vote_post( if remove_vote: if user not in target_voters: return False - target_voters.remove(user) + target_voters.remove(user) # type: ignore else: if user in target_voters: return False - target_voters.add(user) + target_voters.add(user) # type: ignore bot.data[botPy.RE_DATA_POSTS][url]["votes"]["voters_re"] = list(re_voters) bot.data[botPy.RE_DATA_POSTS][url]["votes"]["voters_dc"] = list(dc_voters) diff --git a/src/python/py_websocket.py b/src/python/py_websocket.py index 94b9e70..dbe547c 100644 --- a/src/python/py_websocket.py +++ b/src/python/py_websocket.py @@ -1,13 +1,13 @@ import websockets import asyncio import json +from typing import Any from macros import * import bot as botPy -import data +import py_data import posts -import cmds -import macros +import py_cmds ws_global = None is_connected = False @@ -36,9 +36,10 @@ async def websocket_client(bot: botPy.Bot): while True: response = await ws.recv() - if not response.startswith("json:"): + str_response = str(response) + if not str_response.startswith("json:"): # type: ignore py_print(f"Received from Rust: {response}") - await parse_json(response, bot) + await parse_json(str_response, bot) async def parse_json(response: str, bot: botPy.Bot): @@ -50,7 +51,7 @@ async def parse_json(response: str, bot: botPy.Bot): if json_response["print"]: py_print(f"Received from Rust: {response}") result = await json_to_func(json_response, bot) - await ws_global.ping() + if ws_global is not None: await ws_global.ping() await send_message(f"json:{json.dumps(result)}") except json.JSONDecodeError as e: if bot.args["dev"]: py_print(f"failed to parse json: {json_str}\n reason: {e}") @@ -62,29 +63,29 @@ def run_thread(bot: botPy.Bot): loop.run_until_complete(websocket_client(bot)) -async def json_to_func(v: dict, bot: botPy.Bot) -> dict: - if "type" not in v or "value" not in v or not isinstance(v, dict): +async def json_to_func(v: dict[str, Any], bot: botPy.Bot) -> dict[str, Any]: + if "type" not in v or "value" not in v: if bot.args["dev"]: py_print("JSON is not a dictionary or does not include \"type\" and \"value\" keys.") - return + return result_json(False, True) if v["type"] != "function": v_type = v["type"] if bot.args["dev"]: py_print(f"Type \"{v_type}\" is not supported.") - return + return result_json(False, True) value_supported = True r = False match v["value"]: - case "update_data_file": r = data .write_data (bot) - case "respond_mentions": r = await cmds .respond_to_mention(bot) - case "add_new_posts": r = await posts .add_new_posts (bot, *v["args"]) - case "add_post_url": r = await posts .add_post_url (bot, *v["args"]) - case "remove_post_url": r = data .remove_post (bot, *v["args"]) - 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 "update_cfg": r = await bot .update_cfg_str (*v["args"]) - case "stop_praw": r = await bot .stop () + case "update_data_file": r = py_data.write_data (bot) + case "respond_mentions": r = await py_cmds.respond_to_mention(bot) + case "add_new_posts": r = await posts .add_new_posts (bot, *v["args"]) + case "add_post_url": r = await posts .add_post_url (bot, *v["args"]) + case "remove_post_url": r = py_data.remove_post (bot, *v["args"]) + case "set_approve_post": r = py_data.set_approve_post (bot, *v["args"]) + case "set_vote_post": r = py_data.set_vote_post (bot, *v["args"]) + case "remove_old_posts": r = py_data.remove_old_posts (bot, *v["args"]) + case "update_cfg": r = await bot .update_cfg_str (*v["args"]) + case "stop_praw": r = await bot .stop () case _: value_supported = False print_result = v["print"] @@ -97,5 +98,5 @@ async def json_to_func(v: dict, bot: botPy.Bot) -> dict: return result_json(r, print_result) -def result_json(bool: bool, print_result: bool) -> dict: +def result_json(bool: bool, print_result: bool) -> dict[str, Any]: return {"type": "result", "value": bool, "print": print_result} \ No newline at end of file diff --git a/src/websocket.rs b/src/websocket.rs index 3b2a0f4..fe8826c 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -152,12 +152,12 @@ async fn handle_message(msg: tungstenite::protocol::Message, args: Args, owners: } } tungstenite::Message::Binary(bytes) => { - if args.dev && !args.noping { + if args.dev && args.ping { rs_println!("[Binary] from Python: {:?}", bytes); } } _ => { - if args.dev && !args.noping { + if args.dev && args.ping { rs_println!("Received from Python: [UNKNOWN / OTHER]"); } }