(LARGE COMMIT) refactored until no errors

This commit is contained in:
2026-03-01 14:15:55 +01:00
parent df7583263a
commit ed8904e26c
28 changed files with 322 additions and 289 deletions
+16 -13
View File
@@ -5,7 +5,6 @@ An automation tool primarily made for myself (Byte Dice) but publicly available
> [!CAUTION] > [!CAUTION]
> This tool is not intended for public use outside of the official *Byte Dice Assistant* bots. Expect issues if you host this yourself.\ > This tool is not intended for public use outside of the official *Byte Dice Assistant* bots. Expect issues if you host this yourself.\
> This tool is only designed to run on Windows (10 and 11) and XUbuntu (24.04 and above) and may not work on any other OS.
> [!NOTE] > [!NOTE]
> This bot <ins>never modifies any Reddit content</ins>. All it does and will ever do is read/scrape. > This bot <ins>never modifies any Reddit content</ins>. All it does and will ever do is read/scrape.
@@ -16,6 +15,21 @@ An automation tool primarily made for myself (Byte Dice) but publicly available
**ByteDiceAssistant © 2025 by Byte Dice is licensed under CC BY-NC-SA 4.0.**\ **ByteDiceAssistant © 2025 by Byte Dice is licensed under CC BY-NC-SA 4.0.**\
**You can learn more about copyright by reading the full [license](/LICENSE.txt).** **You can learn more about copyright by reading the full [license](/LICENSE.txt).**
## Environment variables:
(Any Reddit stuff can be disabled.)
| **Name** | **Description** |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ASSISTANT_TOKEN` | The Discord bot token. (Create a Discord bot [here](https://discord.com/developers/docs/intro)!) |
| `ASSISTANT_TOKEN_TEST` | (Optional) A testing Discord bot token. This is only needed when the program is run with `-t` or `--test`. |
| `ASSISTANT_R_ID` | The id for the Reddit bot/account. (Create a Reddit bot [here](https://www.reddit.com/prefs/apps)!) |
| `ASSISTANT_R_TOKEN` | The token for the Reddit bot/account. |
| `ASSISTANT_R_NAME` | The username of the Reddit bot/account. |
| `ASSISTANT_R_PASS` | The password for the Reddit bot/account. |
| `ASSISTANT_OWNERS` | (OPTIONAL) A list of Discord user IDs that "own" the bot. Separate each ID with a single comma and **no** spaces. This will allow the specified user IDs to run root commands such as `/stop`, it will also DM these users when *certain* errors occur. |
| `ASSISTANT_BK_MODS` | (OPTIONAL) Same format as `ASSISTANT_OWNERS` but for people who are allowed to use the `/re_*` commands. |
<!--
## Commands ## Commands
| Name | Category | Description | | Name | Category | Description |
@@ -44,18 +58,6 @@ An automation tool primarily made for myself (Byte Dice) but publicly available
This program uses Rust (v1.82.0) and Python (v3.11.4), you can likely use other versions if they are compatible.\ This program uses Rust (v1.82.0) and Python (v3.11.4), you can likely use other versions if they are compatible.\
It is required to install all used Python modules. You can find those in [req.txt](/req.txt). Installation instructions are in the *How to start the program* section. It is required to install all used Python modules. You can find those in [req.txt](/req.txt). Installation instructions are in the *How to start the program* section.
### Environment variables:
| **Name** | **Description** |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ASSISTANT_TOKEN` | The Discord bot token. (Create a Discord bot [here](https://discord.com/developers/docs/intro)!) |
| `ASSISTANT_TOKEN_TEST` | (Optional) A testing Discord bot token. This is only needed when the program is run with `-t` or `--test`. |
| `ASSISTANT_R_ID` | The id for the Reddit bot/account. (Create a Reddit bot [here](https://www.reddit.com/prefs/apps)!) |
| `ASSISTANT_R_TOKEN` | The token for the Reddit bot/account. |
| `ASSISTANT_R_NAME` | The username of the Reddit bot/account. |
| `ASSISTANT_R_PASS` | The password for the Reddit bot/account. |
| `ASSISTANT_OWNERS` | (OPTIONAL) A list of Discord user IDs that "own" the bot. Separate each ID with a single comma and **no** spaces. This will allow the specified user IDs to run root commands such as `/stop`, it will also DM these users when *certain* errors occur. |
| `ASSISTANT_BK_MODS` | (OPTIONAL) Same format as `ASSISTANT_OWNERS` but for people who are allowed to use the `/re_*` commands. |
### Required permissions: ### Required permissions:
**These are automatically set if you use the [official invite link](https://discord.com/oauth2/authorize?client_id=1212127255795335208&permissions=84992&integration_type=0&scope=bot) or an invite link with the permissions integer set to `84992`.** (The permission integer is this part of the URL `&permissions=84992`) **These are automatically set if you use the [official invite link](https://discord.com/oauth2/authorize?client_id=1212127255795335208&permissions=84992&integration_type=0&scope=bot) or an invite link with the permissions integer set to `84992`.** (The permission integer is this part of the URL `&permissions=84992`)
* Create Invites * Create Invites
@@ -89,3 +91,4 @@ You can find config files in the [cfg/](cfg/) folder. You can also find the defa
* Run `$ cd path/to/extracted/folder`. * Run `$ cd path/to/extracted/folder`.
* Run `$ cargo run`. You can view a list of options by running `$ cargo run -- --help` or `$ cargo run -- -h`. * Run `$ cargo run`. You can view a list of options by running `$ cargo run -- --help` or `$ cargo run -- -h`.
* If you only want to run the Python code, you can either run `$ cargo run -- --py`, or `$ python ./src/python/main.py`. The second option is recommended for better performance. * If you only want to run the Python code, you can either run `$ cargo run -- --py`, or `$ python ./src/python/main.py`. The second option is recommended for better performance.
-->
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::{Context, Error, db_cmds::{add_server, reddit_channel, wwrps_channel}}; use crate::{Context, Error, cmds::db::{add_server, reddit_channel, wwrps_channel}};
#[derive(poise::ChoiceParameter, PartialEq)] #[derive(poise::ChoiceParameter, PartialEq)]
+4 -2
View File
@@ -1,6 +1,6 @@
use rand::{seq::IteratorRandom, Rng}; use rand::{seq::IteratorRandom, Rng};
use crate::{db::generic::get_toml_mutex, lang, messages::send_msg, Context, Error}; use crate::{lang, messages::send_msg, Context, Error};
#[poise::command( #[poise::command(
@@ -16,7 +16,9 @@ pub async fn cmd(
#[description = "Question to ask."] question: String #[description = "Question to ask."] question: String
) -> Result<(), Error> ) -> Result<(), Error>
{ {
let quirky_chance = get_toml_mutex(&ctx.data().cfg).await.unwrap()["commands"]["eight_ball_quirky_chance"].as_float().unwrap(); let quirky_chance = &ctx.data().cfg
["commands"]["eight_ball_quirky_chance"]
.as_float().unwrap();
let is_quirky = rand::rng().random_bool(quirky_chance.clamp(0.0, 1.0)); 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 list = &ctx.data().ball_prompts[if is_quirky { 1 } else { 0 }];
let rand_item = list.iter().choose(&mut rand::rng()); let rand_item = list.iter().choose(&mut rand::rng());
+2 -2
View File
@@ -1,7 +1,7 @@
use poise::serenity_prelude::{ChannelId, Mentionable}; use poise::serenity_prelude::{ChannelId, Mentionable};
use tokio::sync::MutexGuard; use tokio::sync::MutexGuard;
use crate::{Context, Error, db::generic::get_json_mutex, games::wwrps::{RPS, RPSGame, RPSPlayer}, lang, messages::{http_send_msg, send_msg}}; use crate::{Context, Error, games::wwrps::{RPS, RPSGame, RPSPlayer}, lang, messages::{http_send_msg, send_msg}};
#[poise::command( #[poise::command(
@@ -75,7 +75,7 @@ fn results_text(game: &MutexGuard<'_, RPSGame>) -> String {
async fn get_wwrps_channel(ctx: Context<'_>) -> Option<u64> { async fn get_wwrps_channel(ctx: Context<'_>) -> Option<u64> {
let d = get_json_mutex(&ctx.data().discord_data).await.unwrap(); let d = &ctx.data().discord_data.lock().await;
let is_guild = ctx.guild_channel().await.is_some(); let is_guild = ctx.guild_channel().await.is_some();
+4 -5
View File
@@ -1,11 +1,10 @@
use serde_json::json; use serde_json::json;
use crate::db::generic::get_json_mutex;
use crate::db::reddit::{self, POSTS_KEY}; use crate::db::reddit::{self, POSTS_KEY};
use crate::messages::send_msg; use crate::messages::send_msg;
use crate::re_cmds::get::get_post_from_data; use crate::cmds::reddit::get::get_post_from_data;
use crate::{websocket::send_cmd_json, Context, Error}; use crate::{websocket::send_cmd_json, Context, Error};
use crate::re_cmds::generic_fns::{is_bk_mod_msg, send_embed_for_post, to_shorturl}; use crate::cmds::reddit::generic_fns::{is_bk_mod_msg, send_embed_for_post, to_shorturl};
use crate::lang; use crate::lang;
#[poise::command( #[poise::command(
@@ -42,8 +41,8 @@ pub async fn cmd(
return Ok(()); return Ok(());
} }
reddit::update_data(ctx.data()).await; reddit::update_data().await;
let reddit_data = get_json_mutex(&ctx.data().reddit_data).await?; let reddit_data = &ctx.data().reddit_data.lock().await;
if let Some(bk_week) = reddit_data.get(POSTS_KEY) { if let Some(bk_week) = reddit_data.get(POSTS_KEY) {
if let Some(post) = bk_week.get(shorturl) { if let Some(post) = bk_week.get(shorturl) {
+2 -3
View File
@@ -1,6 +1,6 @@
use serde_json::{json, Value}; use serde_json::{json, Value};
use crate::{Context, Error, db::{generic::get_json_mutex, reddit::{self, POSTS_KEY}}, lang, messages::send_msg, re_cmds::generic_fns::{is_bk_mod_msg, to_shorturl}, websocket}; use crate::{Context, Error, db::{reddit::POSTS_KEY}, lang, messages::send_msg, cmds::reddit::generic_fns::{is_bk_mod_msg, to_shorturl}, websocket};
use super::generic_fns::send_embed_for_removed; use super::generic_fns::send_embed_for_removed;
@@ -23,8 +23,7 @@ pub async fn cmd(
let shorturl_u = to_shorturl(&url); let shorturl_u = to_shorturl(&url);
let shorturl = &shorturl_u.unwrap_or(url.clone()); let shorturl = &shorturl_u.unwrap_or(url.clone());
reddit::update_data(ctx.data()).await; let reddit_data = &ctx.data().reddit_data.lock().await;
let reddit_data = get_json_mutex(&ctx.data().reddit_data).await?;
approve_cmd(ctx, shorturl, &reddit_data, !disapprove.unwrap_or(false)).await; approve_cmd(ctx, shorturl, &reddit_data, !disapprove.unwrap_or(false)).await;
+4 -5
View File
@@ -2,7 +2,7 @@ use poise::serenity_prelude::{self as serenity, ChannelId, ComponentInteraction,
use regex::Regex; use regex::Regex;
use serde_json::Value; use serde_json::Value;
use crate::{Context, Data, Error, db::generic::get_toml_mutex, lang, messages::{EmbedOptions, JSON_TEXT_END, JSON_TEXT_START, decode_and_decompress_json, embed_from_options, make_post_embed, make_removed_embed, send_embed, send_msg}}; use crate::{Context, Data, Error, lang, messages::{EmbedOptions, JSON_TEXT_END, JSON_TEXT_START, decode_and_decompress_json, embed_from_options, make_post_embed, make_removed_embed, send_embed, send_msg}};
pub fn is_bk_mod(mod_list: Vec<u64>, uid: u64) -> bool { pub fn is_bk_mod(mod_list: Vec<u64>, uid: u64) -> bool {
return mod_list.contains(&uid); return mod_list.contains(&uid);
@@ -10,7 +10,7 @@ pub fn is_bk_mod(mod_list: Vec<u64>, uid: u64) -> bool {
pub async fn is_bk_mod_msg(ctx: Context<'_>) -> bool { pub async fn is_bk_mod_msg(ctx: Context<'_>) -> bool {
if is_bk_mod(ctx.data().bk_mods.clone(), ctx.author().id.get()) { return true; } if is_bk_mod(ctx.data().env_vars.reddit_mod_discord_ids.clone(), ctx.author().id.get()) { return true; }
let sr = get_readable_subreddits(ctx.data()).await.unwrap(); let sr = get_readable_subreddits(ctx.data()).await.unwrap();
send_msg(ctx, lang!("dc_msg_re_permdeny_not_re_mod", sr), true, true).await; send_msg(ctx, lang!("dc_msg_re_permdeny_not_re_mod", sr), true, true).await;
@@ -19,7 +19,7 @@ pub async fn is_bk_mod_msg(ctx: Context<'_>) -> bool {
pub async fn is_bk_mod_serenity(ctx: &serenity::Context, data: &Data, author: &Member, component: &ComponentInteraction) -> bool { pub async fn is_bk_mod_serenity(ctx: &serenity::Context, data: &Data, author: &Member, component: &ComponentInteraction) -> bool {
if is_bk_mod(data.bk_mods.clone(), author.user.id.get()) { return true; } if is_bk_mod(data.env_vars.reddit_mod_discord_ids.clone(), author.user.id.get()) { return true; }
let sr = get_readable_subreddits(data).await.unwrap(); let sr = get_readable_subreddits(data).await.unwrap();
serenity_send_msg(ctx, component, lang!("dc_msg_re_permdeny_not_re_mod", sr), true).await; serenity_send_msg(ctx, component, lang!("dc_msg_re_permdeny_not_re_mod", sr), true).await;
@@ -70,8 +70,7 @@ pub async fn send_embed_for_removed(ctx: Context<'_>, url: &str, post: &Value) {
pub async fn get_readable_subreddits(data: &Data) -> Result<String, Error> { pub async fn get_readable_subreddits(data: &Data) -> Result<String, Error> {
let d = get_toml_mutex(&data.cfg).await.unwrap(); let sr = data.cfg["reddit"]["subreddits"].as_array().unwrap();
let sr = d["reddit"]["subreddits"].as_array().unwrap();
let sr_str: Vec<&str> = sr let sr_str: Vec<&str> = sr
.iter() .iter()
.map(|v| v.as_str().unwrap()) .map(|v| v.as_str().unwrap())
+3 -3
View File
@@ -1,6 +1,6 @@
use serde_json::Value; use serde_json::Value;
use crate::{Context, Error, db::{generic::get_json_mutex, reddit::{self, POSTS_KEY}}, lang, messages::send_msg, re_cmds::generic_fns::{send_embed_for_post, to_shorturl}, rs_println}; use crate::{Context, Error, db::{reddit::{self, POSTS_KEY}}, lang, messages::send_msg, cmds::reddit::generic_fns::{send_embed_for_post, to_shorturl}, rs_println};
use super::generic_fns::send_embed_for_removed; use super::generic_fns::send_embed_for_removed;
@@ -17,12 +17,12 @@ pub async fn cmd(
#[description = "The post URL."] url: String #[description = "The post URL."] url: String
) -> Result<(), Error> ) -> Result<(), Error>
{ {
reddit::update_data(ctx.data()).await; reddit::update_data().await;
let shorturl_u = to_shorturl(&url); let shorturl_u = to_shorturl(&url);
let shorturl = &shorturl_u.unwrap_or(url.clone()); let shorturl = &shorturl_u.unwrap_or(url.clone());
let reddit_data = get_json_mutex(&ctx.data().reddit_data).await?; let reddit_data = &ctx.data().reddit_data.lock().await;
if let Some(post) = get_post_from_data(ctx, &reddit_data, shorturl).await? { if let Some(post) = get_post_from_data(ctx, &reddit_data, shorturl).await? {
send_embed_for_post(ctx, post, shorturl).await?; send_embed_for_post(ctx, post, shorturl).await?;
+3 -3
View File
@@ -1,6 +1,6 @@
use serde_json::json; use serde_json::json;
use crate::{Context, Error, db::{generic::get_json_mutex, reddit}, lang, messages::send_msg, re_cmds::{generic_fns::{is_bk_mod_msg, send_embed_for_removed, to_shorturl}, get::get_post_from_data}, websocket::send_cmd_json}; use crate::{Context, Error, db::reddit, lang, messages::send_msg, cmds::reddit::{generic_fns::{is_bk_mod_msg, send_embed_for_removed, to_shorturl}, get::get_post_from_data}, websocket::send_cmd_json};
#[poise::command( #[poise::command(
slash_command, slash_command,
@@ -36,8 +36,8 @@ pub async fn cmd(
send_msg(ctx, lang!("dc_msg_re_post_404"), true, true).await; send_msg(ctx, lang!("dc_msg_re_post_404"), true, true).await;
} }
reddit::update_data(ctx.data()).await; reddit::update_data().await;
let reddit_data = get_json_mutex(&ctx.data().reddit_data).await?; let reddit_data = &ctx.data().reddit_data.lock().await;
if let Some(post) = get_post_from_data(ctx, &reddit_data, shorturl).await? { if let Some(post) = get_post_from_data(ctx, &reddit_data, shorturl).await? {
if post["removed"]["removed"].as_bool().unwrap() { if post["removed"]["removed"].as_bool().unwrap() {
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::{lang, messages::send_msg, re_cmds::generic_fns::to_shorturl, Context, Error}; use crate::{lang, messages::send_msg, cmds::reddit::generic_fns::to_shorturl, Context, Error};
#[poise::command( #[poise::command(
+2 -2
View File
@@ -1,6 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::{Context, Error, db::{generic::get_json_mutex, reddit::POSTS_KEY}, re_cmds::generic_fns::send_embed_for_post}; use crate::{Context, Error, db::reddit::POSTS_KEY, cmds::reddit::generic_fns::send_embed_for_post};
#[derive(poise::ChoiceParameter, PartialEq)] #[derive(poise::ChoiceParameter, PartialEq)]
enum TopCategory { enum TopCategory {
@@ -30,7 +30,7 @@ pub async fn cmd(
) -> Result<(), Error> ) -> Result<(), Error>
{ {
let mut all: HashMap<&str, i32> = HashMap::new(); let mut all: HashMap<&str, i32> = HashMap::new();
let posts = &get_json_mutex(&ctx.data().reddit_data).await?[POSTS_KEY]; let posts = &ctx.data().reddit_data.lock().await[POSTS_KEY];
let posts_u = posts.as_object().unwrap(); let posts_u = posts.as_object().unwrap();
for (url, dat) in posts_u { for (url, dat) in posts_u {
+5 -6
View File
@@ -3,7 +3,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use poise::{serenity_prelude::{ChannelId, EditMessage, GetMessages, Http, Message, MessageId, UserId}, ReplyHandle}; use poise::{serenity_prelude::{ChannelId, EditMessage, GetMessages, Http, Message, MessageId, UserId}, ReplyHandle};
use serde_json::{json, Map, Value}; use serde_json::{json, Map, Value};
use crate::{Context, Error, db::{discord::contains_server, generic::{get_json_mutex, get_toml_mutex}, keys::DC_POSTS_CHANNEL_KEY, reddit::{self, POSTS_KEY}}, lang, messages::{edit_reply, embed_from_options, make_post_embed, make_removed_embed, send_embed, send_msg, trim_post_json}, re_cmds::generic_fns::embed_to_json, rs_println, websocket::send_cmd_json}; use crate::{Context, Error, db::{discord::contains_server, keys::DC_POSTS_CHANNEL_KEY, reddit::{self, POSTS_KEY}}, lang, messages::{edit_reply, embed_from_options, make_post_embed, make_removed_embed, send_embed, send_msg, trim_post_json}, cmds::reddit::generic_fns::embed_to_json, rs_println, websocket::send_cmd_json};
#[poise::command( #[poise::command(
slash_command, slash_command,
@@ -39,13 +39,12 @@ pub async fn cmd(
let max_age_u = max_age.unwrap_or(8); let max_age_u = max_age.unwrap_or(8);
let max_age_secs = max_age_u as u64 * (60 * 60 * 24); let max_age_secs = max_age_u as u64 * (60 * 60 * 24);
let max_results_toml = &get_toml_mutex(&ctx.data().cfg).await.unwrap(); let max_results_pre = ctx.data().cfg["reddit"]["fetch_limit"].as_integer().unwrap();
let max_results_pre = max_results_toml["reddit"]["fetch_limit"].as_integer().unwrap();
let max_results_final = max_results.unwrap_or(max_results_pre as u16); let max_results_final = max_results.unwrap_or(max_results_pre as u16);
send_cmd_json("add_new_posts", Some(json!([max_age_secs, max_results_final])), true).await; send_cmd_json("add_new_posts", Some(json!([max_age_secs, max_results_final])), true).await;
reddit::update_data(ctx.data()).await; reddit::update_data().await;
let r_data = get_json_mutex(&ctx.data().reddit_data).await?; let r_data = &ctx.data().reddit_data.lock().await;
let c_id_u = get_c_id(ctx).await; let c_id_u = get_c_id(ctx).await;
@@ -118,7 +117,7 @@ async fn get_c_id(ctx: Context<'_>) -> Option<ChannelId> {
return None; return None;
} }
let d = get_json_mutex(&ctx.data().discord_data).await.unwrap(); let d = &ctx.data().discord_data.lock().await;
let c_id_u = let c_id_u =
d["servers"] d["servers"]
[ctx.guild_id().unwrap().to_string()] [ctx.guild_id().unwrap().to_string()]
+12 -16
View File
@@ -1,6 +1,6 @@
use serde_json::json; use serde_json::json;
use crate::{Context, Error, db::{generic::get_json_mutex, reddit::{self, POSTS_KEY}}, lang, messages::send_msg, re_cmds::generic_fns::{is_bk_mod, send_embed_for_removed, to_shorturl}, websocket::send_cmd_json}; use crate::{Context, Error, db::reddit::{self, POSTS_KEY}, lang, messages::send_msg, cmds::reddit::generic_fns::{is_bk_mod, send_embed_for_removed, to_shorturl}, websocket::send_cmd_json};
#[poise::command( #[poise::command(
slash_command, slash_command,
@@ -16,9 +16,9 @@ pub async fn cmd(
#[description = "Wether to undo your vote or not"] un_vote: Option<bool> #[description = "Wether to undo your vote or not"] un_vote: Option<bool>
) -> Result<(), Error> ) -> Result<(), Error>
{ {
reddit::update_data(ctx.data()).await; reddit::update_data().await;
let uid = ctx.author().id.get(); let uid = ctx.author().id.get();
let re_data = get_json_mutex(&ctx.data().reddit_data).await?; let re_data = &ctx.data().reddit_data.lock().await;
let post_data = re_data[POSTS_KEY].clone(); let post_data = re_data[POSTS_KEY].clone();
let unw_vote = un_vote.unwrap_or(false); let unw_vote = un_vote.unwrap_or(false);
@@ -36,7 +36,7 @@ pub async fn cmd(
let url_data = &post_data[&shorturl]; let url_data = &post_data[&shorturl];
let is_mod = is_bk_mod(ctx.data().bk_mods.clone(), ctx.author().id.get()); let is_mod = is_bk_mod(ctx.data().env_vars.reddit_mod_discord_ids.clone(), ctx.author().id.get());
let voters_dc = url_data["votes"]["voters_dc"].as_array().unwrap(); let voters_dc = url_data["votes"]["voters_dc"].as_array().unwrap();
let mod_voters = url_data["votes"]["mod_voters"].as_array().unwrap(); let mod_voters = url_data["votes"]["mod_voters"].as_array().unwrap();
let voters = if is_mod { mod_voters } else { voters_dc }; let voters = if is_mod { mod_voters } else { voters_dc };
@@ -53,18 +53,14 @@ pub async fn cmd(
let r = send_cmd_json("set_vote_post", Some(json!([shorturl, uid, is_mod, true, unw_vote])), true).await.unwrap(); let r = send_cmd_json("set_vote_post", Some(json!([shorturl, uid, is_mod, true, unw_vote])), true).await.unwrap();
let unw_r = r["value"].as_bool().unwrap(); let unw_r = r["value"].as_bool().unwrap();
if unw_r && !unw_vote && is_mod { if unw_r && !unw_vote && is_mod
send_msg(ctx, lang!("dc_msg_re_vote_mod_success"), true, true).await; { send_msg(ctx, lang!("dc_msg_re_vote_mod_success"), true, true).await; }
} else if unw_r && !unw_vote && !is_mod
else if unw_r && !unw_vote && !is_mod { { send_msg(ctx, lang!("dc_msg_re_vote_success"), true, true).await; }
send_msg(ctx, lang!("dc_msg_re_vote_success"), true, true).await; else if unw_r && unw_vote
} { send_msg(ctx, lang!("dc_msg_re_vote_remove_success"), true, true).await; }
else if unw_r && unw_vote { else
send_msg(ctx, lang!("dc_msg_re_vote_remove_success"), true, true).await; { send_msg(ctx, lang!("dc_msg_re_vote_err"), true, true).await; }
}
else {
send_msg(ctx, lang!("dc_msg_re_vote_err"), true, true).await;
}
return Ok(()); return Ok(());
} }
+3 -3
View File
@@ -1,7 +1,7 @@
pub mod bot_data; pub mod bot_data;
pub mod cfg; pub mod cfg;
pub mod terminal_args;
pub mod discord; pub mod discord;
pub mod generic; pub mod env_vars;
pub mod keys; pub mod keys;
pub mod reddit; pub mod reddit;
pub mod terminal_args;
+9 -10
View File
@@ -1,18 +1,17 @@
use serde_json::Value; use serde_json::Value;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::{db::terminal_args::Args, games::wwrps::RPSGame, lang::Lang}; use crate::{db::{env_vars::AssistantEnv, terminal_args::Args}, games::wwrps::RPSGame, lang::Lang};
pub struct Data { pub struct Data {
pub owners: Vec<u64>,
pub ball_prompts: [Vec<String>; 2],
pub rps_game: Mutex<RPSGame>,
pub reddit_data: Mutex<Option<Value>>,
pub discord_data: Mutex<Option<Value>>,
pub cfg: Mutex<Option<toml::Value>>,
pub bk_mods: Vec<u64>,
pub args: Args, pub args: Args,
pub lang_name: Mutex<String>, pub ball_prompts: [Vec<String>; 2],
pub lang: Mutex<Lang> pub cfg: toml::Value,
pub discord_data: Mutex<Value>,
pub env_vars: AssistantEnv,
pub lang_name: String,
pub lang: Lang,
pub reddit_data: Mutex<Value>,
pub rps_game: Mutex<RPSGame>,
} }
+5 -9
View File
@@ -1,15 +1,13 @@
use std::{fs, io::Write, path::Path}; use std::{fs, io::Write, path::Path};
use serde_json::{Value, json}; use crate::rs_println;
use crate::{Data, rs_println, websocket::send_cmd_json};
static DATA_PATH: &str = "./cfg/cfg.toml"; static DATA_PATH: &str = "./cfg/cfg.toml";
static PRESET_PATH: &str = "./data/defaults/cfg_default.toml"; static PRESET_PATH: &str = "./data/defaults/cfg_default.toml";
pub async fn read_data(data: &Data, wipe: bool) -> Option<Value> { pub async fn read_data(wipe: bool) -> toml::Value {
if !Path::new(DATA_PATH).exists() || wipe { if !Path::new(DATA_PATH).exists() || wipe {
rs_println!( rs_println!(
"{} creating new from preset...", "{} creating new from preset...",
@@ -20,16 +18,14 @@ pub async fn read_data(data: &Data, wipe: bool) -> Option<Value> {
let str_data = fs::read_to_string(DATA_PATH).unwrap(); let str_data = fs::read_to_string(DATA_PATH).unwrap();
let json_data: toml::Value = str_data.parse().unwrap(); let json_data: toml::Value = str_data.parse().unwrap();
let mut cfg_data = data.cfg.lock().await;
*cfg_data = Some(json_data.clone());
let r = send_cmd_json( /*let r = send_cmd_json(
"update_cfg", "update_cfg",
Some(json!([toml::to_string(&json_data).unwrap()])), Some(json!([toml::to_string(&json_data).unwrap()])),
true true
).await; ).await;*/
return r; return json_data;
} }
+8 -14
View File
@@ -8,7 +8,7 @@ static DATA_PATH: &str = "./data/db/dc_data.json";
static PRESET_PATH: &str = "./data/defaults/dc_data_preset.json"; static PRESET_PATH: &str = "./data/defaults/dc_data_preset.json";
pub async fn read_data(data: &Data, wipe: bool) { pub async fn read_data(wipe: bool) -> Value {
if !Path::new(DATA_PATH).exists() || wipe { if !Path::new(DATA_PATH).exists() || wipe {
rs_println!( rs_println!(
"{} creating new from preset...", "{} creating new from preset...",
@@ -19,8 +19,7 @@ pub async fn read_data(data: &Data, wipe: bool) {
let str_data = fs::read_to_string(DATA_PATH).unwrap(); let str_data = fs::read_to_string(DATA_PATH).unwrap();
let json_data = serde_json::from_str(&str_data).unwrap(); let json_data = serde_json::from_str(&str_data).unwrap();
let mut dc_data = data.discord_data.lock().await; return json_data;
*dc_data = json_data;
} }
@@ -51,17 +50,15 @@ pub async fn write_data(data: &Data) {
.unwrap(); .unwrap();
let mut dc_data_lock = data.discord_data.lock().await; let dc_data = data.discord_data.lock().await;
let dc_data = dc_data_lock.as_mut().unwrap(); let json_str = serde_json::to_string_pretty(&dc_data.clone()).unwrap();
let json_str = serde_json::to_string_pretty(dc_data).unwrap();
file.write_all(json_str.as_bytes()).unwrap(); file.write_all(json_str.as_bytes()).unwrap();
} }
pub async fn add_server(data: &Data, server_id: u64) -> Result<(), ()> { pub async fn add_server(data: &Data, server_id: u64) -> Result<(), ()> {
let mut dc_data_lock = data.discord_data.lock().await; let mut dc_data = data.discord_data.lock().await;
let dc_data = dc_data_lock.as_mut().unwrap();
if dc_data.get("servers").is_none() { return Err(()); } if dc_data.get("servers").is_none() { return Err(()); }
@@ -76,8 +73,7 @@ pub async fn add_server(data: &Data, server_id: u64) -> Result<(), ()> {
pub async fn bind_bk(data: &Data, server_id: u64, channel_id: u64) -> Result<(), ()> { pub async fn bind_bk(data: &Data, server_id: u64, channel_id: u64) -> Result<(), ()> {
let mut dc_data_lock = data.discord_data.lock().await; let mut dc_data = data.discord_data.lock().await;
let dc_data = dc_data_lock.as_mut().unwrap();
if dc_data.get("servers").is_none() { return Err(()); } if dc_data.get("servers").is_none() { return Err(()); }
@@ -96,8 +92,7 @@ pub async fn bind_bk(data: &Data, server_id: u64, channel_id: u64) -> Result<(),
pub async fn bind_wwrps(data: &Data, server_id: u64, channel_id: u64) -> Result<(), ()> { pub async fn bind_wwrps(data: &Data, server_id: u64, channel_id: u64) -> Result<(), ()> {
let mut dc_data_lock = data.discord_data.lock().await; let mut dc_data = data.discord_data.lock().await;
let dc_data = dc_data_lock.as_mut().unwrap();
if dc_data.get("servers").is_none() { return Err(()); } if dc_data.get("servers").is_none() { return Err(()); }
@@ -115,8 +110,7 @@ pub async fn bind_wwrps(data: &Data, server_id: u64, channel_id: u64) -> Result<
pub async fn contains_server(data: &Data, server_id: u64) -> bool { pub async fn contains_server(data: &Data, server_id: u64) -> bool {
let dc_data_lock = data.discord_data.lock().await; let dc_data = data.discord_data.lock().await;
let dc_data = dc_data_lock.as_ref().unwrap();
if dc_data.get("servers").is_none() { return false; } if dc_data.get("servers").is_none() { return false; }
+38
View File
@@ -0,0 +1,38 @@
#[derive(Clone)]
pub struct AssistantEnv {
pub token: String,
pub bot_owners: Vec<u64>,
pub reddit_mod_discord_ids: Vec<u64>
}
impl AssistantEnv {
pub fn new(test: bool) -> Self {
let token_name = if test { "ASSISTANT_TOKEN" }
else { "ASSISTANT_TOKEN_TEST" };
return AssistantEnv {
token: string_env(token_name),
bot_owners: vec_u64_env("ASSISTANT_OWNERS"),
reddit_mod_discord_ids: vec_u64_env("ASSISTANT_BK_MODS")
};
}
}
fn string_env(name: &str) -> String {
return std::env::var(name)
.expect(format!("Environment variable \"{}\" not found!", name).as_str())
}
fn vec_u64_env(name: &str) -> Vec<u64> {
let var = std::env::var(name)
.unwrap_or("0".to_string());
return var
.split(",")
.map(|s| s.parse::<u64>()
.expect(format!("Failed to parse {}. Invalid syntax.", name).as_str()))
.collect();
}
-21
View File
@@ -1,21 +0,0 @@
use serde_json::Value;
use tokio::sync::Mutex;
use crate::Error;
pub async fn get_json_mutex(data: &Mutex<Option<Value>>) -> Result<Value, Error> {
let data_lock = data.lock().await;
return match data_lock.as_ref() {
Some(data) => Ok(data.clone()),
None => Err("Cannot get mutex data: The data is corrupted!".into()),
};
}
pub async fn get_toml_mutex(data: &Mutex<Option<toml::Value>>) -> Result<toml::Value, Error> {
let data_lock = data.lock().await;
return match data_lock.as_ref() {
Some(data) => Ok(data.clone()),
None => Err("Cannot get mutex data: The data is corrupted!".into()),
};
}
+6 -7
View File
@@ -2,7 +2,7 @@ use std::{fs, io::Write, path::Path};
use serde_json::Value; use serde_json::Value;
use crate::{Data, rs_println, rs_warnln, websocket::send_cmd_json}; use crate::{rs_println, rs_warnln, websocket::send_cmd_json};
static DATA_PATH: &str = "./data/db/re_data.json"; static DATA_PATH: &str = "./data/db/re_data.json";
@@ -11,7 +11,7 @@ static PRESET_PATH: &str = "./data/defaults/re_data_preset.json";
pub static POSTS_KEY: &str = "posts"; pub static POSTS_KEY: &str = "posts";
pub async fn read_data(data: &Data, wipe: bool) { pub async fn read_data(wipe: bool) -> Value {
if !Path::new(DATA_PATH).exists() || wipe { if !Path::new(DATA_PATH).exists() || wipe {
rs_println!( rs_println!(
"{} creating new from preset...", "{} creating new from preset...",
@@ -21,9 +21,8 @@ pub async fn read_data(data: &Data, wipe: bool) {
} }
let str_data = fs::read_to_string(DATA_PATH).unwrap(); let str_data = fs::read_to_string(DATA_PATH).unwrap();
let json_data: Option<Value> = serde_json::from_str(&str_data).unwrap(); let json_data: Value = serde_json::from_str(&str_data).unwrap();
let mut re_data = data.reddit_data.lock().await; return json_data;
*re_data = json_data;
} }
@@ -45,9 +44,9 @@ fn generate_data() {
} }
pub async fn update_data(data: &Data) { pub async fn update_data() {
send_cmd_json("update_data_file", None, true).await; send_cmd_json("update_data_file", None, true).await;
read_data(data, false).await; read_data(false).await;
} }
+14
View File
@@ -28,3 +28,17 @@ pub struct Args {
#[arg(long, help = "Makes the program not use the schedule system.")] #[arg(long, help = "Makes the program not use the schedule system.")]
pub nosched: bool pub nosched: bool
} }
impl Args {
pub fn new() -> Self
{ return <Args as clap::Parser>::parse(); }
}
impl ToString for Args {
fn to_string(&self) -> String {
return serde_json::to_string(self)
.unwrap_or("[FAILED TO CONVERT TO STRING]".to_string());
}
}
+12 -14
View File
@@ -1,4 +1,3 @@
use crate::db::generic::{get_json_mutex, get_toml_mutex};
use crate::db::reddit::update_data; use crate::db::reddit::update_data;
use crate::r#gen::set_status; use crate::r#gen::set_status;
use crate::messages::{make_post_embed, make_removed_embed, EmbedOptions}; use crate::messages::{make_post_embed, make_removed_embed, EmbedOptions};
@@ -41,9 +40,7 @@ async fn on_ready(ctx: &serenity::Context, data_about_bot: &Ready, data: &Data)
data_about_bot.user.id data_about_bot.user.id
); );
let m_data = get_toml_mutex(&data.cfg).await.unwrap(); set_status(data.cfg.clone(), ctx).await;
set_status(m_data, ctx).await;
} }
@@ -88,9 +85,9 @@ async fn approve_btn(ctx: &serenity::Context, data: &Data, c_member: &Member, co
let c_id = component.channel_id; let c_id = component.channel_id;
let m_id = component.message.id; let m_id = component.message.id;
update_data(data).await; update_data().await;
let new_data = &get_json_mutex(&data.reddit_data) let new_data = &data.reddit_data
.await.unwrap()[POSTS_KEY][&url]; .lock().await[POSTS_KEY][&url];
update_embed(ctx, &url, new_data, &c_id, &m_id).await; update_embed(ctx, &url, new_data, &c_id, &m_id).await;
@@ -123,9 +120,9 @@ async fn remove_btn(ctx: &serenity::Context, data: &Data, c_member: &Member, com
let c_id = component.channel_id; let c_id = component.channel_id;
let m_id = component.message.id; let m_id = component.message.id;
update_data(data).await; update_data().await;
let new_data = &get_json_mutex(&data.reddit_data) let new_data = &data.reddit_data
.await.unwrap()[POSTS_KEY][&url]; .lock().await[POSTS_KEY][&url];
update_embed(ctx, &url, new_data, &c_id, &m_id).await; update_embed(ctx, &url, new_data, &c_id, &m_id).await;
@@ -144,16 +141,17 @@ async fn remove_btn(ctx: &serenity::Context, data: &Data, c_member: &Member, com
async fn vote_btn(ctx: &serenity::Context, data: &Data, c_member: &Member, component: &ComponentInteraction, url: String, vote: bool) -> Result<(), Error> { async fn vote_btn(ctx: &serenity::Context, data: &Data, c_member: &Member, component: &ComponentInteraction, url: String, vote: bool) -> Result<(), Error> {
let uid: u64 = c_member.user.id.into(); let uid: u64 = c_member.user.id.into();
let is_mod = is_bk_mod(data.bk_mods.clone(), uid); let is_mod = is_bk_mod(data.env_vars.reddit_mod_discord_ids.clone(), uid);
let r = send_cmd_json("set_vote_post", Some(json!([&url, uid, is_mod, true, !vote])), true).await.unwrap(); let r = send_cmd_json("set_vote_post", Some(json!([&url, uid, is_mod, true, !vote])), true).await.unwrap();
let c_id = component.channel_id; let c_id = component.channel_id;
let m_id = component.message.id; let m_id = component.message.id;
update_data(data).await; update_data().await;
let new_data = &get_json_mutex(&data.reddit_data) let new_data = &data.reddit_data
.await.unwrap()[POSTS_KEY][&url]; .lock().await[POSTS_KEY][&url];
update_embed(ctx, &url, new_data, &c_id, &m_id).await; update_embed(ctx, &url, new_data, &c_id, &m_id).await;
if r["value"].as_bool().unwrap() { if r["value"].as_bool().unwrap() {
+23 -34
View File
@@ -6,51 +6,41 @@ use poise::serenity_prelude::Client;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use toml::Value; use toml::Value;
use crate::db::env_vars::AssistantEnv;
use crate::db::{cfg, discord, reddit}; use crate::db::{cfg, discord, reddit};
use crate::db::generic::get_toml_mutex;
use crate::games::wwrps::RPSGame; use crate::games::wwrps::RPSGame;
use crate::lang::Lang; use crate::lang::Lang;
use crate::{Args, Cmd, Data, cmds, events, rs_println}; use crate::{Args, Cmd, Data, cmds, events, rs_println};
pub async fn gen_data(args: Args, owners: Vec<u64>) -> Data { pub async fn gen_data(args: Args, env_vars: AssistantEnv) -> Data {
let ball_classic_str = std::fs::read_to_string("./cfg/8-ball_classic.txt").unwrap(); let ball_classic_str = std::fs::read_to_string("./cfg/8-ball_classic.txt").unwrap();
let ball_quirk_str = std::fs::read_to_string("./cfg/8-ball_quirky.txt").unwrap(); let ball_quirk_str = std::fs::read_to_string("./cfg/8-ball_quirky.txt").unwrap();
let ball_classic: Vec<String> = ball_classic_str.lines().map(String::from).collect(); let ball_classic: Vec<String> = ball_classic_str.lines().map(String::from).collect();
let ball_quirk: Vec<String> = ball_quirk_str .lines().map(String::from).collect(); let ball_quirk: Vec<String> = ball_quirk_str .lines().map(String::from).collect();
let mods_env = std::env::var("ASSISTANT_BK_MODS").unwrap_or("0".to_string()); let re_data = reddit:: read_data(args.clone().wipe).await;
let mods_vec_str: Vec<String> = mods_env.split(",").map(String::from).collect(); let dc_data = discord::read_data(args.clone().wipe).await;
let mods_vec_u64: Vec<u64> = mods_vec_str let cf_data = cfg:: read_data(args.clone().wipe).await;
.iter()
.map(|s| s.parse::<u64>().expect("Failed to parse ASSISTANT_BK_MODS. Invalid syntax."))
.collect();
let data = Data { return Data {
owners,
ball_prompts: [ball_classic, ball_quirk],
rps_game: Mutex::new(RPSGame::new()),
bk_mods: mods_vec_u64,
reddit_data: None.into(),
discord_data: None.into(),
cfg: None.into(),
args: args.clone(), args: args.clone(),
ball_prompts: [ball_classic, ball_quirk],
cfg: cf_data,
discord_data: dc_data.into(),
env_vars: env_vars,
lang_name: "".to_string().into(), lang_name: "".to_string().into(),
lang: Lang::new().into() lang: Lang::new().into(),
reddit_data: re_data.into(),
rps_game: Mutex::new(RPSGame::new())
}; };
discord::read_data(&data, args.clone().wipe).await;
reddit:: read_data(&data, args.clone().wipe).await;
cfg:: read_data(&data, args.clone().wipe).await;
return data;
} }
pub async fn gen_bot(data: Data, args: Args) -> Client { pub async fn gen_bot(data: Data) -> Client {
let token = let token =
if !args.test { std::env::var("ASSISTANT_TOKEN").expect("Missing ASSISTANT_TOKEN env var!") } if !data.args.test { std::env::var("ASSISTANT_TOKEN").expect("Missing ASSISTANT_TOKEN env var!") }
else { std::env::var("ASSISTANT_TOKEN_TEST").expect("Missing ASSISTANT_TOKEN_TEST env var!") }; else { std::env::var("ASSISTANT_TOKEN_TEST").expect("Missing ASSISTANT_TOKEN_TEST env var!") };
let intents = serenity::GatewayIntents::all(); let intents = serenity::GatewayIntents::all();
@@ -60,7 +50,7 @@ pub async fn gen_bot(data: Data, args: Args) -> Client {
let token_end_len = token[peek_len..].len(); let token_end_len = token[peek_len..].len();
rs_println!("Token: {}{}", token_peek, "*".repeat(token_end_len)); rs_println!("Token: {}{}", token_peek, "*".repeat(token_end_len));
let own: HashSet<UserId> = data.owners let owner_users: HashSet<UserId> = data.env_vars.bot_owners
.clone() .clone()
.into_iter() .into_iter()
.filter_map(|i| if i == 0 { None } else { Some(UserId::from(i))}) .filter_map(|i| if i == 0 { None } else { Some(UserId::from(i))})
@@ -68,7 +58,7 @@ pub async fn gen_bot(data: Data, args: Args) -> Client {
let framework = poise::Framework::builder() let framework = poise::Framework::builder()
.options(poise::FrameworkOptions { .options(poise::FrameworkOptions {
owners: own, owners: owner_users,
commands: make_cmd_vec(&data).await, commands: make_cmd_vec(&data).await,
event_handler: events::event_handler, event_handler: events::event_handler,
..Default::default() ..Default::default()
@@ -89,7 +79,7 @@ pub async fn gen_bot(data: Data, args: Args) -> Client {
async fn make_cmd_vec(data: &Data) -> Vec<Cmd> { async fn make_cmd_vec(data: &Data) -> Vec<Cmd> {
let mut cmds = vec![ let mut cmds: Vec<Cmd> = vec![
// GENERIC // GENERIC
cmds::generic::help::cmd(), cmds::generic::help::cmd(),
cmds::generic::eight_ball::cmd(), cmds::generic::eight_ball::cmd(),
@@ -110,9 +100,8 @@ async fn make_cmd_vec(data: &Data) -> Vec<Cmd> {
// DATABASE // DATABASE
cmds::db::main_cmd::cmd() cmds::db::main_cmd::cmd()
]; ];
let cfg = get_toml_mutex(&data.cfg).await.unwrap();
let disabled = cfg["commands"]["disabled_categories"] let disabled = data.cfg["commands"]["disabled_categories"]
.as_array() .as_array()
.unwrap() .unwrap()
.iter() .iter()
@@ -125,12 +114,12 @@ async fn make_cmd_vec(data: &Data) -> Vec<Cmd> {
} }
pub async fn set_status(m_data: Value, ctx: &serenity::Context) { pub async fn set_status(cfg: Value, ctx: &serenity::Context) {
let status_str: String; let status_str: String;
let status = m_data["general"]["status"].as_str().unwrap(); let status = cfg["general"]["status"].as_str().unwrap();
let status_c = m_data["general"]["statusCommitNumber"].as_bool().unwrap(); let status_c = cfg["general"]["statusCommitNumber"].as_bool().unwrap();
let status_ec = m_data["general"]["statusExperimentalCommit"].as_bool().unwrap(); let status_ec = cfg["general"]["statusExperimentalCommit"].as_bool().unwrap();
if status_c { if status_c {
let commit_num_r = process::Command::new("git") let commit_num_r = process::Command::new("git")
+7 -4
View File
@@ -66,7 +66,7 @@ macro_rules! warnln {
macro_rules! lang { macro_rules! lang {
($key:expr) => { ($key:expr) => {
{ {
use $crate::{LANG, errln}; /*use $crate::{LANG, errln};
let value = unsafe { let value = unsafe {
LANG LANG
.as_ref() .as_ref()
@@ -76,11 +76,13 @@ macro_rules! lang {
if value.is_none() { errln!("Key not found in LANG JSON: \"{}\"", $key); } if value.is_none() { errln!("Key not found in LANG JSON: \"{}\"", $key); }
value.unwrap().as_str().expect("LANG JSON value is not a string!").to_string() value.unwrap().as_str().expect("LANG JSON value is not a string!").to_string()*/
"hello".to_string()
} }
}; };
($key:expr, $($arg:expr),*) => {{ ($key:expr, $($arg:expr),*) => {{
use $crate::{LANG, errln}; /*use $crate::{LANG, errln};
use formatx::formatx; use formatx::formatx;
let value = unsafe { let value = unsafe {
@@ -93,6 +95,7 @@ macro_rules! lang {
if value.is_none() { errln!("Key not found in LANG JSON: \"{}\"", $key); } if value.is_none() { errln!("Key not found in LANG JSON: \"{}\"", $key); }
let format_str = value.unwrap().as_str().expect("LANG JSON value is not a string!"); let format_str = value.unwrap().as_str().expect("LANG JSON value is not a string!");
formatx!(format_str, $($arg),*).unwrap() formatx!(format_str, $($arg),*).unwrap()*/
"hello".to_string()
}}; }};
} }
+74 -57
View File
@@ -1,24 +1,22 @@
#![warn(unused_extern_crates)]
#![allow(clippy::needless_return)] #![allow(clippy::needless_return)]
#![allow(static_mut_refs)]
mod events;
mod messages;
mod python;
mod macros;
#[allow(unknown_lints)]
mod websocket;
mod cmds; mod cmds;
mod db; mod db;
mod events;
mod games; mod games;
mod schedule;
mod gen; mod gen;
mod lang; mod lang;
mod macros;
mod messages;
mod python;
mod schedule;
mod websocket;
use std::process; use std::process;
use std::thread; use std::thread;
use std::thread::JoinHandle;
use std::time::Duration; use std::time::Duration;
use std::vec; use std::vec;
use std::error::Error as StdErr; use std::error::Error as StdErr;
@@ -31,8 +29,8 @@ use tokio::runtime::Runtime;
use websocket::send_cmd_json; use websocket::send_cmd_json;
use crate::db::bot_data::Data; use crate::db::bot_data::Data;
use crate::db::env_vars::AssistantEnv;
use crate::db::terminal_args::Args; use crate::db::terminal_args::Args;
use crate::db::generic::get_toml_mutex;
use crate::schedule::Schedule; use crate::schedule::Schedule;
@@ -43,79 +41,98 @@ type Cmd = Command<Data, Box<dyn StdErr + Send + Sync>>;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let args = <Args as clap::Parser>::parse(); let args = Args::new();
let args_str = serde_json::to_string(&args).expect("Error serializing args to JSON"); rs_println!("ARGS: {}", args.to_string());
let env_vars = AssistantEnv::new(args.test);
let own_env = std::env::var("ASSISTANT_OWNERS").unwrap_or("0".to_string());
let own_vec_str: Vec<String> = own_env.split(",").map(String::from).collect();
let own_vec_u64: Vec<u64> = own_vec_str
.iter()
.map(|s| s.parse::<u64>().expect("Failed to parse ASSISTANT_OWNERS. Invalid syntax."))
.collect();
rs_println!("Generating and/or fetching data and config..."); rs_println!("Generating and/or fetching data and config...");
let data = gen_data(args.clone(), own_vec_u64.clone()).await; let data = gen_data(args, env_vars).await;
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")); 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.py && !args.rs { // We start this here cuz we have all the data that we need
let _ = python::start(args, *data.lang_name.lock().await).await; if data.args.py && !data.args.rs {
let _ = python::start(
data.args.clone(),
data.lang_name.clone(),
data.env_vars.clone(),
).await;
process::exit(0); process::exit(0);
} }
else if args.rs && ! args.py { else if data.args.rs && ! data.args.py {
start(args, data).await; start_bot(data).await;
process::exit(0); process::exit(0);
} }
rs_println!("ARGS: {}", args_str); let cfg_arr = data.cfg["commands"]["disabled_categories"].as_array().unwrap();
let cfg = get_toml_mutex(&data.cfg).await.unwrap();
let cfg_arr = cfg["commands"]["disabled_categories"].as_array().unwrap();
let run_py = !cfg_arr.iter().any(|val| val.as_str() == Some("re")); let run_py = !cfg_arr.iter().any(|val| val.as_str() == Some("re"));
let rt_rs = Runtime::new().unwrap();
let rt_py = Runtime::new().unwrap();
let python_args = args.clone();
let rust_args = args.clone();
if !run_py { rs_println!("[IMPORTANT] You have disabled the \"re\" commands in the CFG. The app will not run the Python code and the websockets to save resources!"); } if !run_py { rs_println!("[IMPORTANT] You have disabled the \"re\" commands in the CFG. The app will not run the Python code and the websockets to save resources!"); }
let rust = thread::spawn(move || { if !data.args.nosched { start_schedules(data.args.test); }
rt_rs.block_on(async {
if run_py { websocket::start(rust_args.clone(), own_vec_u64.clone()).await; } let python = start_py(
start(rust_args, data).await; data.args.clone(),
}); data.lang_name.clone(),
}); data.env_vars.clone(),
run_py
let python = thread::spawn(move || { );
rt_py.block_on(async { let rust = start_rs(data, run_py);
if run_py { let _ = python::start(python_args, *data.lang_name.lock().await).await; }
});
});
if !args.nosched {
let dur = if args.test { Duration::from_secs(60) } else { Duration::from_secs(60 * 10) };
let schedules: Vec<Schedule> = vec![
(dur, || Box::pin(read_reddit_inbox()))
];
run_schedules(schedules).await;
}
rust.join().unwrap(); rust.join().unwrap();
python.join().unwrap(); python.join().unwrap();
} }
async fn start(args: Args, data: Data) {
let mut bot = gen_bot(data, args).await; fn start_rs(data: Data, run_py: bool) -> JoinHandle<()> {
let rt = Runtime::new().unwrap();
return thread::spawn(move || {
rt.block_on(async {
if run_py { websocket::start(&data).await; }
start_bot(data).await;
});
});
}
fn start_py(
args: Args,
lang_name: String,
env_vars: AssistantEnv,
run_py: bool
) -> JoinHandle<()> {
let rt = Runtime::new().unwrap();
return thread::spawn(move || {
rt.block_on(async {
if run_py { let _ = python::start(args, lang_name, env_vars).await; }
});
});
}
async fn start_bot(data: Data) {
let mut bot = gen_bot(data).await;
rs_println!("Starting Discord bot..."); rs_println!("Starting Discord bot...");
bot.start().await.unwrap(); bot.start().await.unwrap();
} }
async fn start_schedules(test: bool) {
let dur = if test { Duration::from_secs(60) }
else { Duration::from_secs(60 * 10) };
let schedules: Vec<Schedule> = vec![
(dur, || Box::pin(read_reddit_inbox()))
];
run_schedules(schedules).await;
}
async fn read_reddit_inbox() { async fn read_reddit_inbox() {
unsafe { unsafe {
if !websocket::HAS_CONNECTED { return; } if !websocket::HAS_CONNECTED { return; }
+21 -9
View File
@@ -1,7 +1,7 @@
use std::env;
use std::io::{Read, Write}; use std::io::{Read, Write};
use crate::{lang, Args, Context}; use crate::db::bot_data::Data;
use crate::{lang, Context};
use base64::engine::general_purpose; use base64::engine::general_purpose;
use base64::Engine; use base64::Engine;
@@ -199,16 +199,26 @@ pub async fn http_edit_msg(
} }
pub async fn send_dm(msg: String, args: Args, receivers: Vec<u64>) { pub async fn send_dm(msg: String, data: &Data) {
let token: String = let http = Http::new(&data.env_vars.token);
if !args.test { env::var("ASSISTANT_TOKEN") .expect("Missing ASSISTANT_TOKEN env var!") }
else { env::var("ASSISTANT_TOKEN_TEST").expect("Missing ASSISTANT_TOKEN_TEST env var!") };
let c_msg = CreateMessage::new().content(msg);
for uid in data.env_vars.bot_owners.clone() {
if uid == 0 { continue; }
let user = UserId::new(uid);
let _ = user.dm(http.as_ref(), c_msg.clone()).await;
}
}
pub async fn send_dm_min(msg: String, token: String, bot_owners: Vec<u64>) {
let http = Http::new(&token); let http = Http::new(&token);
let c_msg = CreateMessage::new().content(msg); let c_msg = CreateMessage::new().content(msg);
for uid in receivers { for uid in bot_owners {
if uid == 0 { continue; } if uid == 0 { continue; }
let user = UserId::new(uid); let user = UserId::new(uid);
let _ = user.dm(http.as_ref(), c_msg.clone()).await; let _ = user.dm(http.as_ref(), c_msg.clone()).await;
@@ -265,9 +275,11 @@ pub fn make_removed_embed(post_data: &Value, url: &str, ephemeral: bool) -> Embe
let desc = lang!( let desc = lang!(
"dc_msg_embed_re_removed", "dc_msg_embed_re_removed",
if !post_data["removed"]["by"].is_null() { post_data["removed"]["by"].as_str().unwrap() } if !post_data["removed"]["by"].is_null()
{ post_data["removed"]["by"].as_str().unwrap() }
else { &none }, else { &none },
if !post_data["removed"]["reason"].is_null() { post_data["removed"]["reason"].as_str().unwrap() } if !post_data["removed"]["reason"].is_null()
{ post_data["removed"]["reason"].as_str().unwrap() }
else { &none } else { &none }
); );
+15 -24
View File
@@ -1,5 +1,7 @@
use crate::messages::send_dm; use crate::db::env_vars::AssistantEnv;
use crate::{errln, lang, rs_println, Args}; use crate::db::terminal_args::Args;
use crate::messages::send_dm_min;
use crate::{errln, lang, rs_println};
use std::fs; use std::fs;
use std::ffi::CString; use std::ffi::CString;
@@ -9,7 +11,7 @@ use pyo3::prelude::*;
use pyo3::types::PyList; use pyo3::types::PyList;
pub async fn start(args: Args, lang_name: String) -> PyResult<()> { pub async fn start(args: Args, lang_name: String, env_vars: AssistantEnv) -> PyResult<()> {
rs_println!("Running Python program..."); rs_println!("Running Python program...");
let args_str = serde_json::to_string(&args).expect("Error serializing args to JSON"); let args_str = serde_json::to_string(&args).expect("Error serializing args to JSON");
@@ -23,15 +25,12 @@ pub async fn start(args: Args, lang_name: String) -> PyResult<()> {
let py_args = args_str.replace(":true", ":True").replace(":false", ":False"); let py_args = args_str.replace(":true", ":True").replace(":false", ":False");
let app_path: CString; let app_path: CString;
unsafe { app_path = CString::new(format!(
app_path = CString::new( "args = {}\nlang_name = \"{}\"\n{}",
format!("args = {}\nlang_name = \"{}\"\n{}", py_args,
py_args, lang_name,
lang_name.clone(), code
code )).unwrap();
)
).unwrap();
}
let mut traceback: String = String::new(); let mut traceback: String = String::new();
let mut is_error = false; let mut is_error = false;
@@ -57,19 +56,11 @@ pub async fn start(args: Args, lang_name: String) -> PyResult<()> {
}); });
if is_error { if is_error {
let own_env = std::env::var("ASSISTANT_OWNERS").unwrap_or("0".to_string()); send_dm_min(
let own_vec_str: Vec<String> = own_env.split(",").map(String::from).collect(); lang!("dc_msg_dm_python_err", traceback),
let own_vec_u64: Vec<u64> = own_vec_str env_vars.token.clone(),
.iter() env_vars.bot_owners.clone()
.map(|s| s.parse::<u64>().expect("Failed to parse ASSISTANT_OWNERS. Invalid syntax."))
.collect();
send_dm(
lang!("dc_msg_dm_python_err", format!("{}", traceback)),
args,
own_vec_u64
).await; ).await;
errln!("pyO3: {}", traceback); errln!("pyO3: {}", traceback);
} }
+28 -21
View File
@@ -8,7 +8,8 @@ use futures::StreamExt;
use std::sync::Arc; use std::sync::Arc;
use serde_json::{Value, json}; use serde_json::{Value, json};
use crate::messages::send_dm; use crate::db::bot_data::Data;
use crate::messages::send_dm_min;
use crate::{lang, rs_println}; use crate::{lang, rs_println};
use crate::Args; use crate::Args;
@@ -103,17 +104,25 @@ async fn receive_response() -> Option<Value> {
} }
pub async fn start(args: Args, owners: Vec<u64>) { pub async fn start(data: &Data) {
rs_println!("Running local websocket..."); rs_println!("Running local websocket...");
let ip = format!("127.0.0.1:{}", args.port); let ip = format!("127.0.0.1:{}", data.args.port);
let listener = TcpListener::bind(&ip).await.unwrap(); let listener = TcpListener::bind(&ip).await.unwrap();
rs_println!("WebSocket server running on ws://{}", ip); rs_println!("WebSocket server running on ws://{}", ip);
tokio::spawn(handle_connections(listener, args, owners)); tokio::spawn(handle_connections(
listener,
data.env_vars.token.clone(),
data.env_vars.bot_owners.clone()
));
} }
async fn handle_connections(listener: TcpListener, args: Args, owners: Vec<u64>) { async fn handle_connections(
listener: TcpListener,
token: String,
bot_owners: Vec<u64>
) {
while let Ok((stream, _)) = listener.accept().await { while let Ok((stream, _)) = listener.accept().await {
let ws_stream = accept_async(stream).await.unwrap(); let ws_stream = accept_async(stream).await.unwrap();
let (sender, receiver) = ws_stream.split(); let (sender, receiver) = ws_stream.split();
@@ -124,14 +133,17 @@ async fn handle_connections(listener: TcpListener, args: Args, owners: Vec<u64>)
set_sender(sender_arc.clone()).await; set_sender(sender_arc.clone()).await;
set_receiver(receiver_arc.clone()).await; set_receiver(receiver_arc.clone()).await;
while let Some(Ok(msg)) = receiver_arc.lock().await.as_mut().unwrap().next().await { while let Some(Ok(msg)) = receiver_arc.lock().await.as_mut().unwrap().next().await
handle_message(msg, args.clone(), owners.clone()).await; { handle_message(msg, token.clone(), bot_owners.clone()).await; }
}
} }
} }
async fn handle_message(msg: tungstenite::protocol::Message, args: Args, owners: Vec<u64>) { async fn handle_message(
msg: tungstenite::protocol::Message,
token: String,
bot_owners: Vec<u64>
) {
match msg { match msg {
tungstenite::Message::Text(text) => { tungstenite::Message::Text(text) => {
rs_println!("Received from Python: {}", text); rs_println!("Received from Python: {}", text);
@@ -139,7 +151,11 @@ async fn handle_message(msg: tungstenite::protocol::Message, args: Args, owners:
if let Some(stripped) = text.strip_prefix("json:") { if let Some(stripped) = text.strip_prefix("json:") {
let t_json: Value = serde_json::from_str(stripped).unwrap(); let t_json: Value = serde_json::from_str(stripped).unwrap();
if t_json.get("error").is_some() { if t_json.get("error").is_some() {
send_dm(lang!("dc_msg_dm_python_err_socket"), args, owners).await; send_dm_min(
lang!("dc_msg_dm_python_err_socket"),
token,
bot_owners
).await;
} }
} }
@@ -150,16 +166,7 @@ async fn handle_message(msg: tungstenite::protocol::Message, args: Args, owners:
HAS_CONNECTED = true; HAS_CONNECTED = true;
} }
} }
} },
tungstenite::Message::Binary(bytes) => { _ => {}
if args.dev && args.ping {
rs_println!("[Binary] from Python: {:?}", bytes);
}
}
_ => {
if args.dev && args.ping {
rs_println!("Received from Python: [UNKNOWN / OTHER]");
}
}
} }
} }