From 9b5f1bc9d5e196e2419a3b906b896c6173b420a9 Mon Sep 17 00:00:00 2001 From: ByteDice Date: Sat, 15 Feb 2025 17:32:39 +0100 Subject: [PATCH] made bot add new posts to channel --- src/bk_week_cmds.rs | 127 +++++++++++++++++++++++++------------ src/cmds.rs | 12 ++-- src/messages.rs | 54 +++++++++++++++- src/python/posts.py | 3 +- src/python/py_websocket.py | 1 + 5 files changed, 147 insertions(+), 50 deletions(-) diff --git a/src/bk_week_cmds.rs b/src/bk_week_cmds.rs index 3f3318c..dbd10f7 100644 --- a/src/bk_week_cmds.rs +++ b/src/bk_week_cmds.rs @@ -1,11 +1,12 @@ use crate::websocket::send_cmd_json; -use crate::{rs_println, websocket, Context, Error, BK_WEEK}; -use crate::messages::{edit_msg, send_embed, send_msg, EmbedOptions}; +use crate::{messages, rs_println, websocket, Context, Error, BK_WEEK}; +use crate::messages::{edit_msg, send_embed, send_msg}; use crate::data::{self, dc_bind_bk}; use std::fs; -use poise::serenity_prelude::{ChannelId, GetMessages, Message, Timestamp}; +use poise::serenity_prelude::{ChannelId, GetMessages, Message}; +use poise::ReplyHandle; use serde_json::{json, Value}; @@ -96,29 +97,7 @@ async fn get_post_from_data(ctx: Context<'_>, reddit_data: &Value, url: &str) -> async fn send_embed_for_post(ctx: Context<'_>, post: Value, url: &str) -> Result<(), Error> { - let embed_options = EmbedOptions { - desc: format!( - r#"**Spoilers and vote length anonymizer for fair review!** - Upvotes: ||`{:>6}`|| - URL: ||<{}>|| - Added by human: {} - Added by bot: {} - Approved by human: {} - Approved by bot: `[not implemented]`"#, - post["post_data"]["upvotes"].as_i64().unwrap(), - url, - if post["added"] ["by_human"].as_bool().unwrap() { "✅" } else { "❌" }, - if post["added"] ["by_bot"].as_bool().unwrap() { "✅" } else { "❌" }, - if post["approved"]["by_human"].as_bool().unwrap() { "✅" } else { "❌" } - ).trim().to_string(), - title: Some(post["post_data"]["title"].as_str().unwrap().to_string()), - url: Some(url.to_string()), - ts: Some(Timestamp::from_unix_timestamp(post["post_data"]["date_unix"].as_i64().unwrap()).unwrap()), - empheral: true, - ..Default::default() - }; - - send_embed(ctx, embed_options, true).await; + send_embed(ctx, messages::embed_post(&post, url, true), true).await; Ok(()) } @@ -290,6 +269,7 @@ async fn approve_cmd(ctx: Context<'_>, url: &str, reddit_data: &Value, approve: + #[poise::command(slash_command, prefix_command, guild_only)] /// Opposite effects of `/bk_week_approve`. pub async fn bk_week_disapprove( @@ -306,6 +286,8 @@ pub async fn bk_week_disapprove( } + + #[poise::command(slash_command, prefix_command, default_member_permissions = "ADMINISTRATOR", guild_only)] /// Sets the channel where the bot will dump all log info. It's recommended to only run this once. pub async fn bk_week_bind( @@ -331,6 +313,8 @@ async fn send_server_not_in_data_msg(ctx: Context<'_>) { } + + #[poise::command(slash_command, prefix_command, default_member_permissions = "ADMINISTRATOR", guild_only)] /// Updates all logs pub async fn bk_week_update( @@ -338,9 +322,66 @@ pub async fn bk_week_update( #[description = "Only adds new posts, leaves everything else unchanged."] only_add: Option ) -> Result<(), Error> { + let mut p_text = "Fetching new posts & updating data file...".to_string(); + let progress = send_msg(ctx, p_text.clone(), true, true).await; + + send_cmd_json("add_new_posts", json!([])).await; + let r_data = get_reddit_data(ctx).await.unwrap(); + + let c_id = get_c_id(ctx).await.unwrap_or_else(|| 0); + p_text = update_progress(ctx, progress.clone().unwrap(), p_text.clone(), format!("Done!\nReading messages in <#{}>...", c_id)).await; + + if c_id == 0 { + send_msg(ctx, "Could not find bk_week_channel in data!\nHint: Run `/bk_week_bind` in a (preferably read-only) channel.".to_string(), true, true).await; + return Ok(()); + } + + let msgs = read_msgs(ctx, c_id).await; + + p_text = update_progress(ctx, progress.clone().unwrap(), p_text.clone(), "Done!\nParsing messages to JSON...".to_string()).await; + let msgs_json = msgs_to_json(ctx, msgs, &r_data).await; + + p_text = update_progress(ctx, progress.clone().unwrap(), p_text.clone(), "Done!\nAdding new posts...".to_string()).await; + + + // TODO: parse to JSON + // json should be {"added": [], "updated": [], "removed": []} + + + // TODO: add new posts to channel + let weekly_art = r_data["bk_weekly_art_posts"].as_object().unwrap(); + + for url in weekly_art.keys() { + if msgs_json.get(url).is_some() { continue; } + if weekly_art[url].get("removed").is_some() { continue; } + + send_embed(ctx, messages::embed_post(&weekly_art[url], url, false), false).await; + } + + // TODO: edit outdated posts + // TODO: remove removed posts + + /* MSG FORMAT: + {json as spoiler} + {embed} + */ + + + return Ok(()); +} + + +async fn update_progress(ctx: Context<'_>, p: ReplyHandle<'_>, t: String, a_t: String) -> String { + let p_text = format!("{} {}", t, a_t); + edit_msg(ctx, p, p_text.clone()).await; + return p_text; +} + + +async fn get_c_id(ctx: Context<'_>) -> Option { if !data::dc_contains_server(ctx.data(), ctx.guild_id().unwrap().into()).await { send_server_not_in_data_msg(ctx).await; - return Ok(()); + return None; } let d_lock = ctx.data().discord_data.lock().await; @@ -350,16 +391,13 @@ pub async fn bk_week_update( [ctx.guild_id().unwrap().to_string()] ["bk_week_channel"].as_u64().unwrap(); - if c_id == 0 { - send_msg(ctx, "Could not find bk_week_channel in data!\nHint: Run `/bk_week_bind` in a (preferably read-only) channel.".to_string(), true, true).await; - return Ok(()); - } + return Some(c_id); +} + +async fn read_msgs(ctx: Context<'_>, c_id: u64) -> Vec { let c = ChannelId::new(c_id); - let mut p_text = format!("Reading messages in <#{}>...", c_id); - let progress = send_msg(ctx, p_text.clone(), true, true).await; - let b = GetMessages::new().limit(100); let mut msgs = c.messages(ctx.http(), b).await.unwrap(); msgs = msgs.into_iter().filter(|item| item.author.id == ctx.framework().bot_id).collect(); @@ -384,15 +422,22 @@ pub async fn bk_week_update( msgs.extend(filtered_msgs); } - p_text = p_text.as_str().to_owned() + " Done!"; - edit_msg(ctx, progress.unwrap(), p_text).await; + return msgs; +} - // TODO: parse to JSON - // TODO: add new posts to channel - // TODO: edit outdated posts - // TODO: remove removed posts +async fn msgs_to_json(ctx: Context<'_>, msgs: Vec, reddit_data: &Value) -> Value { + let mut msgs_json = json!({"no_change": [], "updated": [], "removed": []}); + for msg in msgs { + let msg_json = serde_json::from_str(&msg.content); + if msg_json.is_ok() { + let u_json: Value = msg_json.unwrap(); + println!("{:?}", u_json); + } - return Ok(()); + else { continue } + } + + return msgs_json; } \ No newline at end of file diff --git a/src/cmds.rs b/src/cmds.rs index 15b52a5..12f4df2 100644 --- a/src/cmds.rs +++ b/src/cmds.rs @@ -73,7 +73,8 @@ pub async fn embed( #[description = "A URL the title is bound to."] url: Option, #[description = "Timestamp at bottom (best to leave empty)."] timestamp: Option, #[description = "Empheral (only visible to you)."] empheral: Option, - #[description = "Shows \"used {Command}\" reply text."] reply: Option + #[description = "Shows \"used {Command}\" reply text."] reply: Option, + #[description = "Text that appears above and outside of the embed"] message: Option ) -> Result<(), Error> { let reply_unwrap = reply.unwrap_or_else(|| false); @@ -81,12 +82,13 @@ pub async fn embed( send_embed( ctx, EmbedOptions { - desc: description, - title, + desc: description.replace("\\n", "\n"), + title: Some(title.unwrap().replace("\\n", "\n")), col: color, url, ts: timestamp, - empheral: empheral.unwrap_or_else(|| false) + empheral: empheral.unwrap_or_else(|| false), + message }, reply_unwrap ).await; @@ -111,7 +113,7 @@ pub async fn send( #[description = "The message to send (NO EMPHERAL)"] msg: String ) -> Result<(), Error> { - send_msg(ctx, msg, false, false).await; + send_msg(ctx, msg.replace("\\n", "\n"), false, false).await; send_msg(ctx, "Mandatory success response, please ignore.".to_string(), true, true).await; return Ok(()); } diff --git a/src/messages.rs b/src/messages.rs index 1c9f879..0710f9f 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -1,5 +1,6 @@ use crate::Context; +use poise::serenity_prelude::json::Value; use poise::{serenity_prelude::CreateMessage, CreateReply, ReplyHandle}; use poise::serenity_prelude::{Color, CreateEmbed, Timestamp}; @@ -10,7 +11,8 @@ pub struct EmbedOptions { pub col: Option, pub url: Option, pub ts: Option, - pub empheral: bool + pub empheral: bool, + pub message: Option } impl Default for EmbedOptions { fn default() -> Self { @@ -20,12 +22,16 @@ impl Default for EmbedOptions { col: None, url: None, ts: None, - empheral: false + empheral: false, + message: None }; } } +static DEFAULT_DC_COL: u32 = 5793266; + + fn none_to_empty(string: Option) -> String { return string.unwrap_or_else(|| "".to_string()); } @@ -64,7 +70,7 @@ pub async fn send_embed( let mut embed = CreateEmbed::new() .title (none_to_empty(options.title)) .description(options.desc) - .colour (Color::new(options.col.unwrap_or_else(|| 5793266))) + .colour (Color::new(options.col.unwrap_or_else(|| DEFAULT_DC_COL))) .url (none_to_empty(options.url)); if options.ts.is_some() { embed = embed.timestamp(options.ts.unwrap()); } @@ -72,6 +78,7 @@ pub async fn send_embed( if reply { let r = CreateReply { embeds: vec![embed], + content: options.message, ephemeral: Some(options.empheral), ..Default::default() }; @@ -98,4 +105,45 @@ pub async fn edit_msg( }; let _ = msg.edit(ctx, r).await; +} + + +pub fn embed_post(post_data: &Value, url: &str, empheral: bool) -> EmbedOptions { + let desc_str = format!( + r#"Sorted by what I think will be most important + Spoilers and vote length anonymizer for fair review! + ## Post Data: + **Media type:** `{}` + **Upvotes:** ||`{:>6}`|| + **URL:** ||<{}>|| + **Media URLS:** + {} + + ## Listing Data: + **Added by:** `{{ human: {}, bot: {} }}` + **Approved by:** `{{ human: {}, bot: [not implemented] }}`"#, + post_data["post_data"]["media_type"].as_str().unwrap(), + post_data["post_data"]["upvotes"].as_i64().unwrap(), + url, + post_data["post_data"]["media_urls"].as_array().unwrap().iter().map(|s| format!("* ||<{}>||", s.as_str().unwrap())).collect::>().join("\n"), + if post_data["added"] ["by_human"].as_bool().unwrap() { "✅" } else { "❌" }, + if post_data["added"] ["by_bot"].as_bool().unwrap() { "✅" } else { "❌" }, + if post_data["approved"]["by_human"].as_bool().unwrap() { "✅" } else { "❌" } + ); + + let trimmed = desc_str + .lines() + .map(|line| line.trim()) + .collect::>() + .join("\n"); + + return EmbedOptions { + title: Some(post_data["post_data"]["title"].as_str().unwrap().to_string()), + desc: trimmed, + col: Some(DEFAULT_DC_COL), + url: Some(url.to_string()), + ts: Some(Timestamp::from_unix_timestamp(post_data["post_data"]["date_unix"].as_i64().unwrap()).unwrap()), + message: Some(format!("||`{{\"{}\":{}}}`||", url, serde_json::to_string(post_data).unwrap())), + empheral + }; } \ No newline at end of file diff --git a/src/python/posts.py b/src/python/posts.py index 50fc9e3..578f59e 100644 --- a/src/python/posts.py +++ b/src/python/posts.py @@ -6,7 +6,7 @@ import bot as botPy from macros import * -async def add_new_posts(bot: botPy.Bot): +async def add_new_posts(bot: botPy.Bot) -> bool: check_emoji = emoji.emojize(":check_mark_button:") cross_emoji = emoji.emojize(":cross_mark:") @@ -49,6 +49,7 @@ async def add_new_posts(bot: botPy.Bot): f"and {not_added} weren't added because they are removed or already existed") data.write_data(bot) + return True async def fetch_posts_with_flair(bot: botPy.Bot, flair_name: str) -> list[models.Submission]: diff --git a/src/python/py_websocket.py b/src/python/py_websocket.py index daa41c9..05ce923 100644 --- a/src/python/py_websocket.py +++ b/src/python/py_websocket.py @@ -64,6 +64,7 @@ async def json_to_func(v: dict, bot: botPy.Bot) -> dict: match v["value"]: case "update_data_file": result = result_json(data.write_data(bot)) + case "add_new_posts": result = result_json(await posts.add_new_posts(bot)) case "add_post_url": result = result_json(await posts.add_post_url(bot, *v["args"])) case "remove_post_url": result = result_json(data.remove_post(bot, *v["args"])) case "set_approve_post": result = result_json(data.set_approve_post(bot, *v["args"]))