From 3f2c628830c6e45b5071a5a685aa144a3d8444a5 Mon Sep 17 00:00:00 2001 From: ByteDice Date: Wed, 5 Feb 2025 19:23:49 +0100 Subject: [PATCH] i added so much that its hard to remember what i added. --- Cargo.toml | 1 + TODO.md | 5 +- help.txt | 13 ++++ src/cmds.rs | 3 +- src/events.rs | 6 +- src/main.rs | 164 +++++++++++++-------------------------------- src/messages.rs | 101 ++++++++++++++++++++++++++++ src/python.rs | 43 ++++++++++++ src/python/main.py | 22 +++--- 9 files changed, 226 insertions(+), 132 deletions(-) create mode 100644 help.txt create mode 100644 src/messages.rs create mode 100644 src/python.rs diff --git a/Cargo.toml b/Cargo.toml index bfeb093..fd2c47d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] poise = "0.6.1" +pyo3 = "0.23.4" rand = "0.9.0" serde_json = "1.0.138" tokio = { version = "1.43.0", features = ["rt-multi-thread"] } diff --git a/TODO.md b/TODO.md index a65d100..1efd833 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,8 @@ - [ ] Discord bot /bk_help command - [x] ~~Scrape the data~~ - [x] ~~Put it in a JSON~~ - - [ ] Ship it to Discord using the Rust bot + - [ ] Multithread so it can run both Discord and Reddit bot!!! + - [ ] Ship Python data to Rust - [ ] Allow updating the data autonomously and via manual commands. - [ ] Manually add posts (via `u/[bot] add` or `/bk_week_add [url]`) - [ ] Manually remove posts (via `/bk_week_remove [url]`) @@ -13,7 +14,7 @@ - [x] ~~Automatically add scraped posts to JSON~~ - [ ] Automatically remove posts older than 7 days from JSON - [x] ~~Function~~ - - [ ] Autonomize + - [ ] Automate - [ ] Automatically approve posts that dont get caught by reverse image search (ris) ### Medium priority: diff --git a/help.txt b/help.txt new file mode 100644 index 0000000..109e3fa --- /dev/null +++ b/help.txt @@ -0,0 +1,13 @@ +--- Run command --- +cargo run [-- {options}] + +Examples: +cargo run +cargo run -- --dev +cargo run -- --py --dev + +--- Options: --- +--h, --help - View this text. +--dev - Run in dev mode. Disables certain security measures. +--rs - Only run the Rust portion of the program. +--py - Only run the Python portion of the program. \ No newline at end of file diff --git a/src/cmds.rs b/src/cmds.rs index 7e30cae..6eced3f 100644 --- a/src/cmds.rs +++ b/src/cmds.rs @@ -1,4 +1,5 @@ -use crate::{send_embed, send_msg, edit_msg, Context, EmbedOptions, Error}; +use crate::{Context, Error}; +use crate::messages::{send_embed, send_msg, edit_msg, EmbedOptions}; use poise::serenity_prelude::{GetMessages, OnlineStatus, Timestamp}; use rand::{seq::IteratorRandom, Rng}; diff --git a/src/events.rs b/src/events.rs index 8d5619c..5b443ec 100644 --- a/src/events.rs +++ b/src/events.rs @@ -9,7 +9,7 @@ pub fn event_handler<'a>( ctx: &'a serenity::Context, event: &'a serenity::FullEvent, _framework: poise::FrameworkContext<'a, Data, Error>, - data: &'a Data, + _data: &'a Data, ) -> Pin> + Send + 'a>> { Box::pin(async move { if let serenity::FullEvent::Ready { data_about_bot } = event { @@ -19,10 +19,6 @@ pub fn event_handler<'a>( data_about_bot.user.id ); - if data.dev { - println!("----- DEV MODE ENABLED -----"); - } - let file_text = std::fs::read_to_string("./data/status.txt").unwrap(); let custom_activity = ActivityData::custom(file_text); // TODO: make custom rich presence diff --git a/src/main.rs b/src/main.rs index 08232c6..3fc0767 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,15 @@ mod cmds; mod events; +mod messages; +mod python; -use poise::{serenity_prelude::{Client, CreateMessage}, CreateReply, ReplyHandle}; -use core::str; +use poise::serenity_prelude::Client; use std::env; -use std::process::Command; use std::process; +use std::thread; +use std::fs; -use poise::serenity_prelude::{self as serenity, Color, CreateEmbed, Timestamp}; +use poise::serenity_prelude as serenity; struct Data { dev: bool, @@ -16,26 +18,11 @@ struct Data { type Error = Box; type Context<'a> = poise::Context<'a, Data, Error>; - -struct EmbedOptions { - desc: String, - title: Option, - col: Option, - url: Option, - ts: Option, - empheral: bool -} -impl Default for EmbedOptions { - fn default() -> Self { - return EmbedOptions { - desc: "default description".to_string(), - title: None, - col: None, - url: None, - ts: None, - empheral: false - }; - } +#[macro_export] +macro_rules! rs_println { + ($($arg:tt)*) => { + println!("RS - {}", format!($($arg)*)); + }; } @@ -43,27 +30,47 @@ impl Default for EmbedOptions { async fn main() { let args: Vec = env::args().collect(); - if args.contains(&"--py".to_string()) { - let output = Command::new("python") - .arg("./src/python/main.py") - .output() - .expect("Failed to launch main.py"); - - let stdout = str::from_utf8(&output.stdout).unwrap_or("Invalid UTF-8 in stdout"); - let stderr = str::from_utf8(&output.stderr).unwrap_or("Invalid UTF-8 in stderr"); - - println!("--- PYTHON OUTPUT:\n\n{}\n", stdout); - println!("--- PYTHON ERROR:\n\n{}", stderr); - + if args.contains(&"--h".to_string()) || args.contains(&"--help".to_string()) { + let help = fs::read_to_string("./help.txt").unwrap_or_else(|_| "No help.txt file found.".to_string()); + println!("HELP MENU:\n{}", help); process::exit(1); } - else { - let data = gen_data(args); - let mut bot = gen_bot(data).await; - println!("Starting bot..."); - bot.start().await.unwrap(); + if args.contains(&"--py".to_string()) + && !args.contains(&"--rs".to_string()) + { + println!("----- PYTHON ONLY MODE -----"); + let _ = python::start(); + process::exit(1); } + else if args.contains(&"--rs".to_string()) + && ! args.contains(&"--py".to_string()) + { + println!("----- RUST ONLY MODE -----"); + start(args).await; + process::exit(1); + } + if args.contains(&"--dev".to_string()) { println!("----- DEV MODE ENABLED -----"); } + + let rust = thread::spawn(|| { + + }); + + let python = thread::spawn(|| { + + }); + + rust.join().unwrap(); + python.join().unwrap(); +} + + +async fn start(args: Vec) { + let data = gen_data(args); + let mut bot = gen_bot(data).await; + + println!("Starting bot..."); + bot.start().await.unwrap(); } @@ -116,79 +123,4 @@ async fn gen_bot(data: Data) -> Client { .framework(framework) .await .unwrap(); -} - - -fn none_to_empty(string: Option) -> String { - return string.unwrap_or_else(|| "".to_string()); -} - - -async fn send_msg( - ctx: Context<'_>, - t: String, - empheral: bool, - reply: bool -) -> Option> -{ - if reply { - let r = CreateReply { - content: Some(t), - ephemeral: Some(empheral), - ..Default::default() - }; - - let msg = ctx.send(r).await; - return Some(msg.unwrap()); - } - else { - let _ = ctx.channel_id().say(ctx.http(), t).await; - return None; - } -} - - -async fn send_embed( - ctx: Context<'_>, - options: EmbedOptions, - reply: bool -) -> Option> -{ - let mut embed = CreateEmbed::new() - .title (none_to_empty(options.title)) - .description(options.desc) - .colour (Color::new(options.col.unwrap_or_else(|| 5793266))) - .url (none_to_empty(options.url)); - - if options.ts.is_some() { embed = embed.timestamp(options.ts.unwrap()); } - - if reply { - let r = CreateReply { - embeds: vec![embed], - ephemeral: Some(options.empheral), - ..Default::default() - }; - - let msg = ctx.send(r).await; - return Some(msg.unwrap()); - } - else { - let r = CreateMessage::new().embeds(vec![embed]); - let _ = ctx.channel_id().send_message(ctx.http(), r).await; - return None; - } -} - - -async fn edit_msg( - ctx: Context<'_>, - msg: ReplyHandle<'_>, - new_text: String -) { - let r = CreateReply { - content: Some(new_text), - ..Default::default() - }; - - let _ = msg.edit(ctx, r).await; } \ No newline at end of file diff --git a/src/messages.rs b/src/messages.rs new file mode 100644 index 0000000..3ede786 --- /dev/null +++ b/src/messages.rs @@ -0,0 +1,101 @@ +use crate::Context; + +use poise::{serenity_prelude::CreateMessage, CreateReply, ReplyHandle}; +use poise::serenity_prelude::{Color, CreateEmbed, Timestamp}; + + +pub struct EmbedOptions { + pub desc: String, + pub title: Option, + pub col: Option, + pub url: Option, + pub ts: Option, + pub empheral: bool +} +impl Default for EmbedOptions { + fn default() -> Self { + return EmbedOptions { + desc: "default description".to_string(), + title: None, + col: None, + url: None, + ts: None, + empheral: false + }; + } +} + + +fn none_to_empty(string: Option) -> String { + return string.unwrap_or_else(|| "".to_string()); +} + + +pub async fn send_msg( + ctx: Context<'_>, + t: String, + empheral: bool, + reply: bool +) -> Option> +{ + if reply { + let r = CreateReply { + content: Some(t), + ephemeral: Some(empheral), + ..Default::default() + }; + + let msg = ctx.send(r).await; + return Some(msg.unwrap()); + } + else { + let _ = ctx.channel_id().say(ctx.http(), t).await; + return None; + } +} + + +pub async fn send_embed( + ctx: Context<'_>, + options: EmbedOptions, + reply: bool +) -> Option> +{ + let mut embed = CreateEmbed::new() + .title (none_to_empty(options.title)) + .description(options.desc) + .colour (Color::new(options.col.unwrap_or_else(|| 5793266))) + .url (none_to_empty(options.url)); + + if options.ts.is_some() { embed = embed.timestamp(options.ts.unwrap()); } + + if reply { + let r = CreateReply { + embeds: vec![embed], + ephemeral: Some(options.empheral), + ..Default::default() + }; + + let msg = ctx.send(r).await; + return Some(msg.unwrap()); + } + else { + let r = CreateMessage::new().embeds(vec![embed]); + let _ = ctx.channel_id().send_message(ctx.http(), r).await; + return None; + } +} + + +pub async fn edit_msg( + ctx: Context<'_>, + msg: ReplyHandle<'_>, + new_text: String +) { + let r = CreateReply { + content: Some(new_text), + ..Default::default() + }; + + let _ = msg.edit(ctx, r).await; +} \ No newline at end of file diff --git a/src/python.rs b/src/python.rs new file mode 100644 index 0000000..28e20d6 --- /dev/null +++ b/src/python.rs @@ -0,0 +1,43 @@ +use crate::rs_println; + +use std::fs; +use std::ffi::CString; +use pyo3::prelude::*; +use pyo3::types::PyList; +use std::env; + + +pub fn start() -> PyResult<()> { + rs_println!("Running Python program..."); + + let path = concat!(env!("CARGO_MANIFEST_DIR"), "\\src\\python"); + + let code = get_code(&(path.to_owned() + "\\main.py")); + let app_path = CString::new(code).unwrap(); + + pyo3::prepare_freethreaded_python(); + let from_python = Python::with_gil(|py| -> PyResult> { + let syspath = py + .import("sys")? + .getattr("path")? + .downcast_into::()?; + + syspath.insert(0, path)?; + let empty = CString::new("").unwrap(); + + let app: Py = PyModule::from_code(py, &app_path, &empty, &empty)? + .getattr("run")? + .into(); + + return app.call0(py); + }); + + println!("py: {}", from_python?); + return Ok(()); +} + +fn get_code(path: &str) -> String { + return fs::read_to_string(path) + .expect("Failed to read Python file.") + .to_string(); +} \ No newline at end of file diff --git a/src/python/main.py b/src/python/main.py index 81ebd39..701e5af 100644 --- a/src/python/main.py +++ b/src/python/main.py @@ -1,7 +1,13 @@ -from praw import models +def py_print(*args: str): + print("Py -", " ".join(args)) + +py_print("Importing external modules...") + import emoji import sys +py_print("Importing internal modules") + import bot as botPy import data import reddit @@ -11,22 +17,22 @@ def main(): sys.stdout.reconfigure(encoding="utf-8") bot = botPy.Bot() - print("Reading data...") + py_print("Reading data...") data.read_data(bot) check_emoji = emoji.emojize(":check_mark_button:") cross_emoji = emoji.emojize(":cross_mark:") - print("Fetching posts...") + py_print("Fetching posts...") posts = reddit.fetch_posts_with_flair(bot, "Original Art") - print("Evaluating posts...\n\n") + py_print("Evaluating posts...\n\n") for post in posts: media = reddit.has_media(post) media_urls = "\n ".join(media[3]) - print( - f"{post.title}", + 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 {media_urls}\n" @@ -48,5 +54,5 @@ def main(): data.write_data(bot) -if __name__ == "__main__": - main() \ No newline at end of file +py_print("Running main()...") +main() \ No newline at end of file