From 7fc23896cbce60d41f38339512052935eb7a62dc Mon Sep 17 00:00:00 2001 From: ByteDice Date: Mon, 17 Feb 2025 22:48:03 +0100 Subject: [PATCH] added voting system, bug fixes, and a ton more --- TODO.md | 14 ++-- bk_week_help_dc.md | 16 ++-- bk_week_help_re.md | 3 + data/bk_mods.json | 29 +++++++ data/reddit_data_preset.json | 10 ++- src/bk_week_cmds.rs | 145 ++++++++++++++++++++++++++--------- src/main.rs | 14 ++-- src/messages.rs | 27 ++++++- src/python/data.py | 88 +++++++++++++++------ src/python/posts.py | 1 - src/python/py_websocket.py | 1 + 11 files changed, 262 insertions(+), 86 deletions(-) create mode 100644 data/bk_mods.json diff --git a/TODO.md b/TODO.md index 62d8d71..d4cd6ba 100644 --- a/TODO.md +++ b/TODO.md @@ -5,11 +5,13 @@ ¨ - - [ ] Security that only allows bk mods to run these commands + + - [ ] Allow updating the data autonomously and via manual commands. - - [ ] 30-minute schedule + - [ ] 30-minute schedule when bot adds posts - [ ] Manually add posts - [ ] via `u/[bot] add` + - [ ] 5 minute schedule when user adds post @@ -18,7 +20,7 @@ - [ ] Automatically remove posts older than 7 days from JSON - [x] ~~Function~~ - [ ] Automate - - [ ] Automatically approve posts that dont get caught by reverse image search (ris) + - [ ] Automatically approve posts that don't get caught by reverse image search (ris) @@ -29,9 +31,9 @@ ### Medium priority: -- [ ] View single rule (/rule {rulename}) +- [ ] View single rule (/rule {rule_name}) - [ ] Postfix calculator -- [ ] Postfic generator +- [ ] Postfix generator - [ ] JSON -> BPS class init - [ ] BPS args -> JSON - [ ] Random tip (from ByteDice.net/data/loadingScreenTips.json) @@ -44,7 +46,7 @@ - [ ] Weekly coding competition * Same as particle of the week but with coding - [ ] Content update sender - * Automatically sends sneek peeks (like commit history or manual) of projects when theyre updated + * Automatically sends sneak peeks (like commit history or manual) of projects when they're updated - [ ] Language TLDR command * Shows a TLDR with pros/cons on a programming language - [ ] PowerPlate info viewer diff --git a/bk_week_help_dc.md b/bk_week_help_dc.md index d258f78..8eaddd3 100644 --- a/bk_week_help_dc.md +++ b/bk_week_help_dc.md @@ -1,16 +1,18 @@ # Discord Commands -## `/bk_week_add [url] [approve]` +## `/bk_week_add [url] ` 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. +- **``**: (OPTIONAL) `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]` +## `/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`. +- **``**: (OPTIONAL) `true` or `false`, set to `true` if you want to undo an approval. ## `/bk_week_update` -Updates all data in the binded Discord channel. It's reccommended to run this rarely. -## `/bk_week_bind` +Updates all data in the bound Discord channel. It's recommended to run this rarely. +## `/bk_week_vote [url] ` +Adds a vote to a post. The intended use for votes is for a "moderator picks" and a "community picks" section of the weekly art. +You can vote on as many posts as you want, but only once per post. +## `/bk_admin_bind` Binds the current channel as the channel where the bk_week data is sent and updated in. No binding means the only way to view the data is using `/bk_week_get`. ### **Examples** ``` diff --git a/bk_week_help_re.md b/bk_week_help_re.md index 04f0ae8..48f4fbf 100644 --- a/bk_week_help_re.md +++ b/bk_week_help_re.md @@ -4,6 +4,9 @@ To execute a command on the Reddit bot, include `u/ByteDiceAssistant [args]` in ## `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.** +## `bk_week_vote` +Adds a vote to the post. (see `/bk_week_vote` in the Discord help). +- **Anyone can use this command.** ### **Examples** ``` "u/ByteDiceAssistant bk_week_add" diff --git a/data/bk_mods.json b/data/bk_mods.json new file mode 100644 index 0000000..430a035 --- /dev/null +++ b/data/bk_mods.json @@ -0,0 +1,29 @@ +{ + "bk1": { + "discord": [ + 697149665166229614, + 526842473062989864, + 1171015493537247273, + 1156779840956010496, + 1021944396473716847, + 1085622021024653362, + 1223480842525872148, + 1155349278655520858 + ], + "reddit": [ + + ] + }, + "bk2": { + "discord": [ + 1004132854227292272, + 1031352334745354282, + 1173039143245332520, + 1281040971710201857, + 772049174672703528 + ], + "reddit": [ + + ] + } +} \ No newline at end of file diff --git a/data/reddit_data_preset.json b/data/reddit_data_preset.json index f630828..587fc1c 100644 --- a/data/reddit_data_preset.json +++ b/data/reddit_data_preset.json @@ -1,5 +1,4 @@ { - "file_created_correctly": true, "bk_weekly_art_posts": { "EXAMPLE VALUE": { "post_data": { @@ -9,7 +8,11 @@ "media_type": null, "media_urls": [] }, - "nominated_by_human": true, + "votes": { + "voters_re": ["bytedice"], + "voters_dc": [0], + "mod_voters": [0] + }, "added": { "by_human": false, "by_bot": true @@ -21,7 +24,8 @@ }, "EXAMPLE VALUE DELETED": { "removed": true, - "removed_by": "ME!!!!" + "removed_by": "ME!!!!", + "remove_reason": "i HATED that post >:(" } } } \ No newline at end of file diff --git a/src/bk_week_cmds.rs b/src/bk_week_cmds.rs index 5c03cd5..34bc605 100644 --- a/src/bk_week_cmds.rs +++ b/src/bk_week_cmds.rs @@ -17,6 +17,20 @@ enum HelpOptions { } +fn is_bk_mod(mod_list: Value, uid: u64) -> bool { + let obj = mod_list.as_object().unwrap(); + let bk1_arr = obj["bk1"]["discord"].as_array().unwrap(); + let bk2_arr = obj["bk2"]["discord"].as_array().unwrap(); + + return bk1_arr.contains(&json!(uid)) || bk2_arr.contains(&json!(uid)); +} + + +async fn not_bk_mod_msg(ctx: Context<'_>) { + send_msg(ctx, "Permission denied: You are not a moderator of r/boykisser or r/boykisser2".to_string(), true, true).await; +} + + #[poise::command( slash_command, prefix_command @@ -48,11 +62,11 @@ pub async fn bk_week_help( -#[poise::command(slash_command, prefix_command, guild_only)] +#[poise::command(slash_command, prefix_command)] /// Retrieves the data of a single post just for you. The data has to be within the database to work. pub async fn bk_week_get( ctx: Context<'_>, - #[description = "The post URL"] url: String + #[description = "The post URL."] url: String ) -> Result<(), Error> { data::update_re_data(ctx.data()).await; @@ -68,10 +82,10 @@ pub async fn bk_week_get( async fn get_reddit_data(ctx: Context<'_>) -> Result { let data_lock = ctx.data().reddit_data.lock().await; - match data_lock.as_ref() { + return match data_lock.as_ref() { Some(data) => Ok(data.clone()), None => Err("Reddit data is corrupted".into()), - } + }; } @@ -146,14 +160,19 @@ async fn send_data_corrupted_message(ctx: Context<'_>, url: &str) { -#[poise::command(slash_command, prefix_command, guild_only)] +#[poise::command(slash_command, prefix_command)] /// Fetches a post from Reddit and adds it to the database. pub async fn bk_week_add( ctx: Context<'_>, - #[description = "The post URL"] url: String, + #[description = "The post URL."] url: String, #[description = "Wether to approve it after adding it"] approve: Option ) -> Result<(), Error> { + if !is_bk_mod(ctx.data().bk_mods_json.clone(), ctx.author().id.get()) { + not_bk_mod_msg(ctx).await; + return Ok(()); + } + data::update_re_data(ctx.data()).await; let reddit_data = get_reddit_data(ctx).await.unwrap(); @@ -206,15 +225,21 @@ async fn send_updated_msg(ctx: Context<'_>, url: &str) { -#[poise::command(slash_command, prefix_command, guild_only)] +#[poise::command(slash_command, prefix_command)] /// Removes a post from the database. It will show who last removed it. pub async fn bk_week_remove( ctx: Context<'_>, - #[description = "The post URL"] url: String + #[description = "The post URL."] url: String, + #[description = "The reason of the removal."] reason: Option ) -> Result<(), Error> { + if !is_bk_mod(ctx.data().bk_mods_json.clone(), ctx.author().id.get()) { + not_bk_mod_msg(ctx).await; + return Ok(()); + } + let auth = &ctx.author().name; - let r = send_cmd_json("remove_post_url", json!([&url, &auth])).await.unwrap(); + let r = send_cmd_json("remove_post_url", json!([&url, &auth, &reason])).await.unwrap(); if r["value"].as_bool().unwrap() { send_msg( @@ -234,17 +259,23 @@ pub async fn bk_week_remove( -#[poise::command(slash_command, prefix_command, guild_only)] +#[poise::command(slash_command, prefix_command)] /// Approves a post in the database. Approving posts tells the bot that it's original. pub async fn bk_week_approve( ctx: Context<'_>, - #[description = "The post URL"] url: String + #[description = "The post URL."] url: String, + #[description = "Wether to approve or disapprove the post"] disapprove: Option ) -> Result<(), Error> { + if !is_bk_mod(ctx.data().bk_mods_json.clone(), ctx.author().id.get()) { + not_bk_mod_msg(ctx).await; + return Ok(()); + } + data::update_re_data(ctx.data()).await; let reddit_data = get_reddit_data(ctx).await.unwrap(); - approve_cmd(ctx, &url, &reddit_data, true).await; + approve_cmd(ctx, &url, &reddit_data, !disapprove.unwrap_or_else(|| false)).await; return Ok(()); } @@ -277,27 +308,9 @@ 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( - ctx: Context<'_>, - #[description = "The post URL"] url: String -) -> Result<(), Error> -{ - data::update_re_data(ctx.data()).await; - let reddit_data = get_reddit_data(ctx).await.unwrap(); - - approve_cmd(ctx, &url, &reddit_data, false).await; - - return Ok(()); -} - - - - #[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( +pub async fn bk_admin_bind( ctx: Context<'_> ) -> Result<(), Error> { @@ -333,23 +346,23 @@ pub async fn bk_week_update( let progress = send_msg(ctx, p_text.clone(), true, true).await; send_cmd_json("add_new_posts", json!([])).await; + data::update_re_data(ctx.data()).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!("✅\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; + send_msg(ctx, "Could not find bk_week_channel in data!\nHint: Run (or tell an admin to run) `/bk_admin_bind` in a (preferably read-only) channel.".to_string(), true, true).await; return Ok(()); } + p_text = update_progress(ctx, progress.clone().unwrap(), p_text.clone(), format!("✅\nReading messages in <#{}>...", c_id)).await; let msgs = read_msgs(ctx, c_id).await; p_text = update_progress(ctx, progress.clone().unwrap(), p_text.clone(), "✅\nParsing messages to JSON...".to_string()).await; let msgs_json = msgs_to_json(msgs, &r_data).await; p_text = update_progress(ctx, progress.clone().unwrap(), p_text.clone(), "✅\nAdding new posts...".to_string()).await; - let weekly_art = r_data["bk_weekly_art_posts"].as_object().unwrap(); for url in weekly_art.keys() { @@ -399,6 +412,7 @@ pub async fn bk_week_update( } update_progress(ctx, progress.clone().unwrap(), p_text.clone(), "✅\n## Done!".to_string()).await; + return Ok(()); } @@ -458,6 +472,8 @@ async fn read_msgs(ctx: Context<'_>, c_id: u64) -> Vec { } +// TODO: all posts get updated for some reason (i think, debug print pls) +// TODO: also doesn't edit messages if they got removed async fn msgs_to_json<'a>(msgs: Vec, reddit_data: &'a Value) -> Value { let mut msgs_json: Value = json!({"no_change": {}, "updated": {}, "removed": {}, "duplicates": {}}); @@ -506,9 +522,10 @@ async fn msgs_to_json<'a>(msgs: Vec, reddit_data: &'a Value) -> Value { } } - if u_json["added"] != re_url["added"] - || u_json["approved"] != re_url["approved"] + if u_json["added"] != re_url["added"] + || u_json["approved"] != re_url["approved"] || u_json["post_data"]["upvotes"] != re_url["post_data"]["upvotes"] + || u_json["votes"] != re_url["votes"] { u_json.as_object_mut().unwrap().insert("msg_id".to_string(), Value::String(msg.id.clone().to_string())); @@ -525,4 +542,60 @@ async fn msgs_to_json<'a>(msgs: Vec, reddit_data: &'a Value) -> Value { } return msgs_json; +} + + + + +#[poise::command(slash_command, prefix_command)] +pub async fn bk_week_vote( + ctx: Context<'_>, + #[description = "The post URL."] url: String, + #[description = "Wether to undo your vote or not"] un_vote: Option +) -> Result<(), Error> +{ + data::update_re_data(ctx.data()).await; + let uid = ctx.author().id.get(); + let re_data = get_reddit_data(ctx).await.unwrap(); + let post_data = re_data["bk_weekly_art_posts"].clone(); + let unw_vote = un_vote.unwrap_or_else(|| false); + + if post_data.get(&url).is_none() { + send_post_not_found_message(ctx, &url).await; + return Ok(()); + } + + let url_data = &post_data[&url]; + + let is_mod = is_bk_mod(ctx.data().bk_mods_json.clone(), ctx.author().id.get()); + let voters_dc = url_data["votes"]["voters_dc"].as_array().unwrap(); + let mod_voters = url_data["votes"]["mod_voters"].as_array().unwrap(); + let voters = if is_mod { mod_voters } else { voters_dc }; + + if voters.contains(&json!(uid)) && !unw_vote { + send_msg(ctx, "Couldn't cast a vote: You have already voted on this post!".to_string(), true, true).await; + return Ok(()); + } + else if !voters.contains(&json!(uid)) && unw_vote { + send_msg(ctx, "Couldn't remove your vote: You haven't voted on this post yet!".to_string(), true, true).await; + return Ok(()); + } + + let r = send_cmd_json("set_vote_post", json!([url, uid, is_mod, true, unw_vote])).await.unwrap(); + let unw_r = r["value"].as_bool().unwrap(); + + if unw_r && !unw_vote && is_mod { + send_msg(ctx, "Successfully voted (as moderator vote)!".to_string(), true, true).await; + } + else if unw_r && !unw_vote && !is_mod { + send_msg(ctx, "Successfully voted!".to_string(), true, true).await; + } + else if unw_r && unw_vote { + send_msg(ctx, "Successfully removed vote!".to_string(), true, true).await; + } + else { + send_msg(ctx, "Failed to vote/un-vote: Unknown internal error".to_string(), true, true).await; + } + + return Ok(()); } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 34368d5..823229d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,6 @@ use serde_json; // TODO: bot command permissions -// TODO: bk_mod verification system #[derive(Parser, Serialize, Clone)] @@ -49,6 +48,7 @@ struct Data { byte_dice_id: u64, reddit_data: Mutex>, discord_data: Mutex>, + bk_mods_json: Value, args: Args // TODO: schedules } @@ -66,6 +66,7 @@ async fn main() { 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 -----"); } if args.py && !args.rs { println!("----- PYTHON ONLY MODE -----"); @@ -115,13 +116,16 @@ async fn start(args: Args) { async fn gen_data(args: Args) -> Data { let ball_classic_str = std::fs::read_to_string("./data/8-ball_classic.txt").unwrap(); - let ball_quirk_str = std::fs::read_to_string("./data/8-ball_quirky.txt").unwrap(); + let ball_quirk_str = std::fs::read_to_string("./data/8-ball_quirky.txt").unwrap(); + let bk_mods_str = std::fs::read_to_string("./data/bk_mods.json").unwrap(); let ball_classic: Vec = ball_classic_str.lines().map(String::from).collect(); let ball_quirk: Vec = ball_quirk_str .lines().map(String::from).collect(); + let bk_mods: Value = serde_json::from_str(&bk_mods_str).unwrap(); let data = Data { ball_prompts: [ball_classic, ball_quirk], + bk_mods_json: bk_mods, byte_dice_id: 697149665166229614, reddit_data: None.into(), discord_data: None.into(), @@ -168,9 +172,9 @@ async fn gen_bot(data: Data, args: Args) -> Client { bk_week_cmds::bk_week_add(), bk_week_cmds::bk_week_remove(), bk_week_cmds::bk_week_approve(), - bk_week_cmds::bk_week_disapprove(), - bk_week_cmds::bk_week_bind(), - bk_week_cmds::bk_week_update() + bk_week_cmds::bk_admin_bind(), + bk_week_cmds::bk_week_update(), + bk_week_cmds::bk_week_vote() ], event_handler: events::event_handler, ..Default::default() diff --git a/src/messages.rs b/src/messages.rs index d82bac3..dfd3a18 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -137,15 +137,25 @@ pub async fn edit_msg( pub fn embed_post(post_data: &Value, url: &str, empheral: bool) -> EmbedOptions { let media_type = &post_data["post_data"]["media_type"]; + let re_votes = &post_data["votes"]["voters_re"].as_array().unwrap().len(); + let dc_votes = &post_data["votes"]["voters_dc"].as_array().unwrap().len(); + 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}`|| + **Post upvotes:** ||`{:>6}`|| **URL:** ||<{}>|| **Media URLS:** {} + + ## Voting data: + **Moderator votes:** ||`{:>6}`|| + **Community votes:** ||`{:>6}`|| + * ||**From Reddit:** `{:>6}`|| + * ||**From Discord:** `{:>6}`|| + ## Listing Data: **Added by:** `{{ human: {}, bot: {} }}` **Approved by:** `{{ human: {}, bot: [not implemented] }}`"#, @@ -153,6 +163,12 @@ pub fn embed_post(post_data: &Value, url: &str, empheral: bool) -> EmbedOptions 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"), + + post_data["votes"]["mod_voters"].as_array().unwrap().len(), + re_votes + dc_votes, + re_votes, + dc_votes, + 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 { "❌" } @@ -169,7 +185,7 @@ pub fn embed_post(post_data: &Value, url: &str, empheral: bool) -> EmbedOptions return EmbedOptions { title: Some(post_data["post_data"]["title"].as_str().unwrap().to_string()), - desc: format!("{}\nJSON: ||`{}`||", trimmed, serde_json::to_string(&json_min).unwrap()), + desc: format!("{}\n\nJSON: ||`{}`||", trimmed, serde_json::to_string(&json_min).unwrap()), 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()), @@ -185,7 +201,12 @@ pub fn embed_post(post_data: &Value, url: &str, empheral: bool) -> EmbedOptions pub fn embed_post_removed(post_data: &Value, url: &str, empheral: bool) -> EmbedOptions { return EmbedOptions { title: Some("REMOVED!".to_string()), - desc: format!("## Removed by `{}`\nJSON: ||`{}`||", post_data["removed_by"].as_str().unwrap(), serde_json::to_string(&post_data).unwrap()), + desc: format!( + "## Removed by `{}`\n**Reason:** {}\n\nJSON: ||`{}`||", + post_data["removed_by"].as_str().unwrap(), + post_data["remove_reason"].as_str().unwrap(), + serde_json::to_string(&post_data).unwrap() + ), col: Some(REMOVED_DC_COL), url: Some(url.to_string()), empheral, diff --git a/src/python/data.py b/src/python/data.py index 39a2fef..885a9e9 100644 --- a/src/python/data.py +++ b/src/python/data.py @@ -12,17 +12,19 @@ BK_WEEKLY: Final[str] = "bk_weekly_art_posts" class PostData: def __init__( self, - url: str, - title: str, - upvotes: int, - date_unix: int, - media_type: str, - media_urls: list[str], - nominated_by_human: bool = False, - added_by_human: bool = False, - added_by_bot: bool = False, + url: str, + title: str, + upvotes: int, + date_unix: int, + media_type: str, + media_urls: list[str], + voters_re: list[str] = [], + voters_dc: list[int] = [], + mod_voters: list[int] = [], + added_by_human: bool = False, + added_by_bot: bool = False, approved_by_human: bool = False, - approved_by_ris: bool = False + approved_by_ris: bool = False ): self.url = url self.title = title @@ -30,7 +32,9 @@ class PostData: self.date_unix = date_unix self.media_type = media_type self.media_urls = media_urls - self.nominated_by_human = nominated_by_human + self.voters_re = voters_re + self.voters_dc = voters_dc + self.mod_voters = mod_voters self.added_by_human = added_by_human self.added_by_bot = added_by_bot self.approved_by_human = approved_by_human @@ -45,7 +49,11 @@ class PostData: "media_type": self.media_type, "media_urls": self.media_urls }, - "nominated_by_human": self.nominated_by_human, + "votes": { + "voters_re": self.voters_re, + "voters_dc": self.voters_dc, + "mod_voters": self.mod_voters + }, "added": { "by_human": self.added_by_human, "by_bot": self.added_by_bot @@ -83,9 +91,6 @@ def read_data(bot: botPy.Bot) -> bool: data_str = bot.data_f.read() json_data = json.loads(data_str) bot.data = json_data - - if not bot.data["file_created_correctly"]: - raise Exception("reddit_data.json file wasn't created properly. Delete the file and retry.") return True @@ -104,22 +109,20 @@ def add_post_to_data(bot: botPy.Bot, new_data: PostData, bypass_conditions: bool py_print(f"Added post \"{new_data.url}\" (Conditions bypassed)") return True + # not sure what this is for updated = False - 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 new_data.url not in bot.data[BK_WEEKLY] or updated: bot.data[BK_WEEKLY][new_data.url] = new_data.to_json() if bot.args["dev"]: py_print(f"Added post \"{new_data.url}\"") return True - - elif "removed" in bot.data[BK_WEEKLY][new_data.url]: - py_print(f"Failed to add post \"{new_data.url}\": Removed flag is True.") - return False + + if "removed" not in bot.data[BK_WEEKLY][new_data.url]: + updated = new_data.upvotes != bot.data[BK_WEEKLY][new_data.url]["post_data"] else: - py_print(f"Failed to add post \"{new_data.url}\": Already exists.") + py_print(f"Failed to add post \"{new_data.url}\": Removed flag is True.") return False @@ -131,9 +134,44 @@ def set_approve_post(bot: botPy.Bot, approved: bool, url: str) -> bool: return False -def remove_post(bot: botPy.Bot, url: str, removed_by: str = "UNKNOWN") -> bool: +def remove_post(bot: botPy.Bot, url: str, removed_by: str = "UNKNOWN", reason: str = "None") -> bool: if url in bot.data[BK_WEEKLY]: - bot.data[BK_WEEKLY][url] = { "removed": True, "removed_by": removed_by } + bot.data[BK_WEEKLY][url] = { "removed": True, "removed_by": removed_by, "remove_reason": reason } return True else: - return False \ No newline at end of file + return False + + +def set_vote_post( + bot: botPy.Bot, + url: str, + user: str | int, + mod_vote: bool = False, + from_dc: bool = False, + remove_vote: bool = False, +) -> bool: + if url not in bot.data[BK_WEEKLY]: + return False + + votes = bot.data[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"]) + + target_voters = mod_voters if mod_vote else (dc_voters if from_dc else re_voters) + + if remove_vote: + if user not in target_voters: + return False + target_voters.remove(user) + + else: + if user in target_voters: + 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) + + return True \ No newline at end of file diff --git a/src/python/posts.py b/src/python/posts.py index 5ec7d7f..707f5b6 100644 --- a/src/python/posts.py +++ b/src/python/posts.py @@ -50,7 +50,6 @@ async def add_new_posts(bot: botPy.Bot) -> bool: f" {without_media} had no media, " + f"and {not_added} weren't added because they are removed or already existed") - data.write_data(bot) return True diff --git a/src/python/py_websocket.py b/src/python/py_websocket.py index 05ce923..d40d855 100644 --- a/src/python/py_websocket.py +++ b/src/python/py_websocket.py @@ -68,6 +68,7 @@ async def json_to_func(v: dict, bot: botPy.Bot) -> dict: 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"])) + case "set_vote_post": result = result_json(data.set_vote_post(bot, *v["args"])) case "stop_praw": result = result_json(await bot.stop()) case _: value_supported = False