From 069d6152729d433be22643908d09330d696d509d Mon Sep 17 00:00:00 2001 From: Byte Dice Date: Wed, 12 Feb 2025 17:43:35 +0100 Subject: [PATCH] migrated to asyncPRAW (i fucking hate it but it made reddit shut the fuck up) --- TODO.md | 2 +- bk_week_help.md | 39 ---------------------------------- bk_week_help_dc.md | 16 ++++++++++++++ bk_week_help_re.md | 12 +++++++++++ src/bk_week_cmds.rs | 23 +++++++++++++++++--- src/cmds.rs | 3 +++ src/python.rs | 6 ++---- src/python/bot.py | 42 ++++++++++++++++++++++++------------- src/python/data.py | 6 ++++-- src/python/main.py | 24 ++++++++++++++------- src/python/posts.py | 20 ++++++++---------- src/python/py_websocket.py | 14 ++++++++----- src/python/reddit.py | 43 -------------------------------------- src/websocket.rs | 1 + 14 files changed, 121 insertions(+), 130 deletions(-) delete mode 100644 bk_week_help.md create mode 100644 bk_week_help_dc.md create mode 100644 bk_week_help_re.md delete mode 100644 src/python/reddit.py diff --git a/TODO.md b/TODO.md index befef35..6c76eda 100644 --- a/TODO.md +++ b/TODO.md @@ -10,7 +10,7 @@ - [ ] via `u/[bot] add` - [x] ~~via `/bk_week_add [url]`~~ - [ ] Manually remove posts via `/bk_week_remove [url]` - - [ ] Manually approve posts via `/bk_week_approve [url]` + - [x] ~~Manually approve posts via `/bk_week_approve [url]`~~ - [ ] Manually un-approve posts via `/bk_week_disapprove [url]` - [x] ~~Automatically add scraped posts to JSON~~ - [ ] Automatically remove posts older than 7 days from JSON diff --git a/bk_week_help.md b/bk_week_help.md deleted file mode 100644 index 0f38b2e..0000000 --- a/bk_week_help.md +++ /dev/null @@ -1,39 +0,0 @@ -# Discord Commands - -### `/bk_week_add [url] [approve]` -Adds a URL to the list of posts. This is done automatically by the bot for certain posts. -- **`[approve]`**: `true` or `false`, determines whether to pre-approve the post. - -### `/bk_week_remove [url]` -Removes an existing URL from the list of posts. - -### `/bk_week_approve [url]` -Flags the post as **human_approved**, confirming that the artwork is original. - -### `/bk_week_disapprove [url]` -Reverses the effect of `/bk_week_approve`. - -#### **Examples** -``` -/bk_week_add https://reddit.com/post_url false -/bk_week_add https://reddit.com/post_url -/bk_week_approve https://reddit.com/post_url -``` - ---- - -# Reddit Commands - -To execute a command on the Reddit bot, include `"u/ByteDiceAssistant [args]"` in a comment. -- **`[args]`**: The command you want to run. - -### `bk_week_add` -Adds the post to the list of posts. -- **Only moderators of a subreddit or the OP (Original Poster) can use this command.** - -#### **Examples** -``` -"u/ByteDiceAssistant bk_week_add" -"Cool art, let me add that. u/ByteDiceAssistant bk_week_add" -"Cool art. Just gonna u/ByteDiceAssistant bk_week_add so it can become featured." -``` \ No newline at end of file diff --git a/bk_week_help_dc.md b/bk_week_help_dc.md new file mode 100644 index 0000000..4b38b04 --- /dev/null +++ b/bk_week_help_dc.md @@ -0,0 +1,16 @@ +# Discord Commands +## `/bk_week_add [url] [approve]` +Adds a URL to the list of posts. This is done automatically by the bot for certain posts. +- **`[approve]`**: `true` or `false`, determines whether to pre-approve the post. +## `/bk_week_remove [url]` +Removes an existing URL from the list of posts. +## `/bk_week_approve [url]` +Flags the post as **human_approved**, confirming that the artwork is original. +## `/bk_week_disapprove [url]` +Reverses the effect of `/bk_week_approve`. +### **Examples** +``` +/bk_week_add https://reddit.com/post_url false +/bk_week_add https://reddit.com/post_url +/bk_week_approve https://reddit.com/post_url +``` \ No newline at end of file diff --git a/bk_week_help_re.md b/bk_week_help_re.md new file mode 100644 index 0000000..8cf99f8 --- /dev/null +++ b/bk_week_help_re.md @@ -0,0 +1,12 @@ +# Reddit Commands +To execute a command on the Reddit bot, include `u/ByteDiceAssistant [args]` in a comment. +- **`[args]`**: The command you want to run and its arguments. +## `bk_week_add` +Adds the post to the list of posts. +- **Only moderators of a subreddit or the OP (Original Poster) can use this command.** +### **Examples** +``` +"u/ByteDiceAssistant bk_week_add" +"Cool art, let me add that. u/ByteDiceAssistant bk_week_add" +"Cool art. Just gonna u/ByteDiceAssistant bk_week_add so it can become featured." +``` \ No newline at end of file diff --git a/src/bk_week_cmds.rs b/src/bk_week_cmds.rs index 42d2bc0..880cd53 100644 --- a/src/bk_week_cmds.rs +++ b/src/bk_week_cmds.rs @@ -8,12 +8,31 @@ use poise::serenity_prelude::Timestamp; use serde_json::{json, Value}; +#[derive(poise::ChoiceParameter, PartialEq)] +enum HelpOptions { + Discord, + Reddit +} + + #[poise::command(slash_command, prefix_command)] pub async fn bk_week_help( ctx: Context<'_>, + #[description = "Discord or Reddit help."] option: HelpOptions ) -> Result<(), Error> { - let help = fs::read_to_string("./bk_week_help.md").unwrap(); + let help: String; + + if option == HelpOptions::Discord { + help = fs::read_to_string("./bk_week_help_dc.md").unwrap(); + } + else if option == HelpOptions::Reddit { + help = fs::read_to_string("./bk_week_help_re.md").unwrap(); + } + else { + help = "Unknown error!\nError trace: `bk_week_cmds.rs -> bk_week_help() -> option is not valid`.".to_string(); + } + send_msg(ctx, help, true, true).await; data::read_dc_data(ctx.data()); @@ -218,8 +237,6 @@ pub async fn bk_week_approve( else { send_post_not_found_message(ctx, &url).await; } - - let result = websocket::send_cmd_json("set_approve_post", json!([true, &url])); return Ok(()); } diff --git a/src/cmds.rs b/src/cmds.rs index 1ac825b..5838504 100644 --- a/src/cmds.rs +++ b/src/cmds.rs @@ -1,11 +1,13 @@ use std::process; +use crate::websocket::send_cmd_json; use crate::{data, Context, Error}; use crate::messages::{send_embed, send_msg, edit_msg, EmbedOptions}; use poise::serenity_prelude::{GetMessages, OnlineStatus, Timestamp, UserId}; use rand::{seq::IteratorRandom, Rng}; use regex::Regex; +use serde_json::json; #[poise::command(slash_command, prefix_command)] @@ -35,6 +37,7 @@ pub async fn stop( let msg = send_msg(ctx, "Saving data...".to_string(), true, true).await.unwrap(); data::write_dc_data(ctx.data()); data::write_re_data().await; + send_cmd_json("stop_praw", json!([])).await; edit_msg(ctx, msg, "Saving data... Done!\nShutting down...".to_string()).await; ctx.serenity_context().set_presence(None, OnlineStatus::Invisible); diff --git a/src/python.rs b/src/python.rs index 1b33578..87977d2 100644 --- a/src/python.rs +++ b/src/python.rs @@ -24,11 +24,9 @@ pub fn start(args: String) -> PyResult<()> { syspath.insert(0, path)?; let empty = CString::new("").unwrap(); - let app: Py = PyModule::from_code(py, &app_path, &empty, &empty)? - .getattr("main")? - .into(); + let app: Py = PyModule::from_code(py, &app_path, &empty, &empty)?.into(); - return app.call0(py); + return Ok(app); }); if from_python.is_err() { errln!("pyO3: {:?}", from_python); } diff --git a/src/python/bot.py b/src/python/bot.py index bbd4ac0..99a9138 100644 --- a/src/python/bot.py +++ b/src/python/bot.py @@ -1,30 +1,42 @@ from io import TextIOWrapper -from praw import models -import praw +import asyncpraw as praw import os from macros import * + class Bot: args: dict = {"NO_RUST": True, "dev": True, "py": True, "port": 2920} password: str = os.environ.get("ASSISTANT_R_PASS") - secret: str = os.environ.get("ASSISTANT_R_TOKEN") + secret: str = os.environ.get("ASSISTANT_R_TOKEN") if password is None: py_error("Environment variable \"ASSISTANT_R_PASS\" is null!") if secret is None: py_error("Environment variable \"ASSISTANT_R_TOKEN\" is null!") - r: praw.Reddit = praw.Reddit( - client_id = "iCSRWS6PMlTLwmylCJRYmA", - client_secret = secret, - username = "ByteDiceAssistant", - password = password, - user_agent = "Byte Dice Assistant by u/RandomPersonDotExe aka u/Byte_Dice" - ) - sr: models.Subreddit = r.subreddit("bytedicetesting") #r.subreddit("boykisser") - data_f: TextIOWrapper = None - data: dict = {} + def __init__(self): + self.r: praw.Reddit = praw.Reddit( + client_id="iCSRWS6PMlTLwmylCJRYmA", + client_secret=self.secret, + username="ByteDiceAssistant", + password=self.password, + user_agent="Byte Dice Assistant by u/RandomPersonDotExe aka u/Byte_Dice" + ) + self.sr = None + self.data_f: TextIOWrapper = None + self.data: dict = {} - def set_args(self, args: dict): - self.args = args \ No newline at end of file + async def initialize(self): + self.sr = await self.r.subreddit("bytedicetesting")#boykisser") + + async def set_args(self, args: dict): + self.args = args + + async def stop(self) -> bool: + if self.r: + await self.r.close() + py_print("Stopped Reddit bot.") + return True + + return False \ No newline at end of file diff --git a/src/python/data.py b/src/python/data.py index 2ad7229..ae778b1 100644 --- a/src/python/data.py +++ b/src/python/data.py @@ -95,12 +95,14 @@ def write_data(bot: botPy.Bot) -> bool: 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() - py_print(f"Added post \"{new_data.url}\" (Conditions bypassed)") + if bot.args["dev"]: + py_print(f"Added post \"{new_data.url}\" (Conditions bypassed)") return True if new_data.url not in bot.data[BK_WEEKLY]: bot.data[BK_WEEKLY][new_data.url] = new_data.to_json() - py_print(f"Added post \"{new_data.url}\"") + if bot.args["dev"]: + py_print(f"Added post \"{new_data.url}\"") return True elif "removed" in bot.data[BK_WEEKLY][new_data.url]: diff --git a/src/python/main.py b/src/python/main.py index 99c533e..eba1a58 100644 --- a/src/python/main.py +++ b/src/python/main.py @@ -7,13 +7,21 @@ from macros import * import bot as botPy import data import py_websocket +import posts -def main(): +async def main(): sys.stdout.reconfigure(encoding="utf-8") + py_print("Creating Reddit bot...") bot = botPy.Bot() - try: bot.set_args(args) + + await bot.initialize() + py_print(f"Successfully created Reddit bot: {await bot.r.user.me()}") + + # args is supposed to be undefined. + # It gets defined in Rust. + try: await bot.set_args(args) except NameError: py_print("No command args found from Rust. Don't worry though, we have backup in place.") @@ -24,16 +32,18 @@ def main(): if not bot.args["py"]: py_print("Connecting to local websocket...") - ws_thread = threading.Thread(target=py_websocket.run_thread, args=(bot,)) - ws_thread.start() + await py_websocket.websocket_client(bot) + """ ws_thread = threading.Thread(target=py_websocket.run_thread, args=(bot,)) + ws_thread.start() """ while not py_websocket.is_connected: py_print("Awaiting connection...") time.sleep(1) continue - asyncio.run(py_websocket.send_message("[Connection test] Hello from Python!")) + await py_websocket.send_message("[Connection test] Hello from Python!") + + await bot.stop() -if __name__ == "__main__": - main() \ No newline at end of file +asyncio.run(main()) \ No newline at end of file diff --git a/src/python/posts.py b/src/python/posts.py index 3a7b467..37c0389 100644 --- a/src/python/posts.py +++ b/src/python/posts.py @@ -1,17 +1,17 @@ import emoji -from praw import models +from asyncpraw import models import data import bot as botPy from macros import * -def add_new_posts(bot: botPy.Bot): +async def add_new_posts(bot: botPy.Bot): check_emoji = emoji.emojize(":check_mark_button:") cross_emoji = emoji.emojize(":cross_mark:") py_print("Fetching posts...") - posts = fetch_posts_with_flair(bot, "Original Art") + posts = await fetch_posts_with_flair(bot, "Original Art") py_print("Evaluating posts...") @@ -51,11 +51,11 @@ def add_new_posts(bot: botPy.Bot): data.write_data(bot) -def fetch_posts_with_flair(bot: botPy.Bot, flair_name: str) -> list[models.Submission]: +async def fetch_posts_with_flair(bot: botPy.Bot, flair_name: str) -> list[models.Submission]: posts: list[models.Submission] = [] # ~36 OG-art posts per week, round limit to 50, 75 or 100 - 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=10): posts.append(post) return posts @@ -91,10 +91,8 @@ def has_media(post: models.Submission) -> tuple[bool, str, int, list[str]]: return (media_type != None, media_type, media_count, media_urls) -# TODO: convert to asyncpraw because praw wont SHUT THE FUCK UP -# Gosh i gotta handle so much pain dont i? -def from_url(bot: botPy.Bot, url: str) -> tuple[bool, models.Submission]: - post = bot.r.submission(url=url) +async def from_url(bot: botPy.Bot, url: str) -> tuple[bool, models.Submission]: + post: models.Submission = await bot.r.submission(url=url) if hasattr(post, "id"): return True, post @@ -116,8 +114,8 @@ def get_post_details(post: models.Submission) -> data.PostData: ) -def add_post_url(bot, url: str) -> bool: - result, post = from_url(bot, url) +async def add_post_url(bot, url: str) -> bool: + result, post = await from_url(bot, url) if not result: return result diff --git a/src/python/py_websocket.py b/src/python/py_websocket.py index 4fe0bfa..7be9a91 100644 --- a/src/python/py_websocket.py +++ b/src/python/py_websocket.py @@ -36,7 +36,6 @@ async def parse_json(response: str, bot: botPy.Bot): json_response = json.loads(json_str) result = await json_to_func(json_response, bot) await ws_global.ping() - print(result) 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}") @@ -60,12 +59,17 @@ async def json_to_func(v: dict, bot: botPy.Bot) -> dict: result = {"type": "result", "value": False} match v["value"]: - case "update_data_file": result = {"type": "result", "value": data.write_data(bot)} - case "add_post_url": result = {"type": "result", "value": posts.add_post_url(bot, *v["args"])} - case "set_approve_post": result = {"type": "result", "value": data.set_approve_post(bot, *v["args"])} + case "update_data_file": result = result_json(data.write_data(bot)) + case "add_post_url": result = result_json(await posts.add_post_url(bot, *v["args"])) + case "set_approve_post": result = result_json(data.set_approve_post(bot, *v["args"])) + case "stop_praw": result = result_json(bot.stop()) case _: value_supported = False if bot.args["dev"] and not value_supported: py_print(f"Value {v['value']} is not supported") - return result \ No newline at end of file + return result + + +def result_json(bool: bool) -> dict: + return {"type": "result", "value": bool} \ No newline at end of file diff --git a/src/python/reddit.py b/src/python/reddit.py deleted file mode 100644 index 42dce30..0000000 --- a/src/python/reddit.py +++ /dev/null @@ -1,43 +0,0 @@ -from praw import models - -import bot as botPy - - -def fetch_posts_with_flair(bot: botPy.Bot, flair_name: str) -> list[models.Submission]: - posts: list[models.Submission] = [] - - # ~36 OG-art posts per week, round limit to 50 or 75 - for post in bot.sr.search(f"flair:\"{flair_name}\"", sort="new", limit=10): - posts.append(post) - - return posts - - -def has_media(post: models.Submission) -> tuple[bool, str, int, list[str]]: - media_type: str = None - media_count = 0 - media_urls: list[str] = [] - - if hasattr(post, "post_hint"): - media_type = post.post_hint - media_count = 1 - media_urls.append(post.url) - - elif getattr(post, "is_gallery", False): - if not post.is_gallery: pass - media_type = "multiple" - - gallery_items = getattr(post, "gallery_data", {}).get("items", []) - media_metadata = getattr(post, "media_metadata", {}) - - media_count = len(gallery_items) - - for item in gallery_items: - media_id = item.get("media_id") - image_url = media_metadata.get(media_id, {}).get("s", {}).get("u") - - if image_url: - media_urls.append(image_url) - - - return (media_type != None, media_type, media_count, media_urls) \ No newline at end of file diff --git a/src/websocket.rs b/src/websocket.rs index 4985061..0dff001 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -59,6 +59,7 @@ pub async fn send_cmd_json(func_name: &str, func_args: Value) -> Option { } let r = receive_response().await; + rs_println!("Received from Python: [RESPONSE] {:?}", r); return r; } }