Merge pull request #2 from ByteDice/experimental_new_commands

Experimental new commands
This commit is contained in:
Byte Dice
2026-02-24 18:54:39 +01:00
committed by GitHub
19 changed files with 210 additions and 78 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ flate2 = "1.1.2"
formatx = "0.2.3" formatx = "0.2.3"
futures = "0.3.31" futures = "0.3.31"
poise = "0.6.1" poise = "0.6.1"
pyo3 = "0.23.4" pyo3 = "0.28.2"
rand = "0.9.0" rand = "0.9.0"
regex = "1.11.1" regex = "1.11.1"
serde = "1.0.217" serde = "1.0.217"
+4 -3
View File
@@ -58,10 +58,11 @@ It is required to install all used Python modules. You can find those in [req.tx
### 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`)
* Send Messages * Create Invites
* Read Message History
* View Channels
* Embed Links * Embed Links
* Read Message History
* Send Messages
* View Channels
### Configuration: ### Configuration:
You can find config files in the [cfg/](cfg/) folder. You can also find the default configs in the [data/defaults/cfg_default.toml](data/defaults/cfg_default.toml) file.\ You can find config files in the [cfg/](cfg/) folder. You can also find the default configs in the [data/defaults/cfg_default.toml](data/defaults/cfg_default.toml) file.\
+8
View File
@@ -6,6 +6,13 @@ lang = "en"
# The discord bots status text # The discord bots status text
status = "🎲 https://bytedice.net" status = "🎲 https://bytedice.net"
# Adds "(Commit #123)" at the end of the status
statusCommitNumber = true
# Changes "(Commit #123)" into "(Experimental #123)"
# Requires statusCommitNumber to be true
statusExperimentalCommit = false
[reddit] [reddit]
# Which subreddits the bot will scan when executing "re"-category commands. # Which subreddits the bot will scan when executing "re"-category commands.
# Is automatically disabled when `disabled_categories` includes "re". # Is automatically disabled when `disabled_categories` includes "re".
@@ -31,6 +38,7 @@ disabled_categories = [
# "fun", # "fun",
# "help" # "help"
# "re" # "re"
# "debug"
] ]
# The chance (between 0..1) for the `/8_ball` command to output a "quirky" answer. # The chance (between 0..1) for the `/8_ball` command to output a "quirky" answer.
+4 -1
View File
@@ -19,6 +19,8 @@
"dc_msg_err_trace": "Unknown error!\nError trace: {0}", "dc_msg_err_trace": "Unknown error!\nError trace: {0}",
"dc_msg_failed_shorturl_conversion": "Couldn't convert to shortURL: Invalid Reddit URL format.", "dc_msg_failed_shorturl_conversion": "Couldn't convert to shortURL: Invalid Reddit URL format.",
"dc_msg_mandatory_response": "Mandatory response message, please ignore.", "dc_msg_mandatory_response": "Mandatory response message, please ignore.",
"dc_msg_no_perms": "Missing permission in that server: {0}",
"dc_msg_not_in_guild": "I am not a part of that guild.",
"dc_msg_owner_data_save_complete": "Saving data... Done!\nShutting down...", "dc_msg_owner_data_save_complete": "Saving data... Done!\nShutting down...",
"dc_msg_owner_data_save": "Saving data...", "dc_msg_owner_data_save": "Saving data...",
"dc_msg_owner_shutdown_failed_confirmation": "Failed to shut down: Invalid confirmation.", "dc_msg_owner_shutdown_failed_confirmation": "Failed to shut down: Invalid confirmation.",
@@ -62,5 +64,6 @@
"py_re_response_weekly_add": "Successfully added your post to the weekly art submissions! Thank you for participating!", "py_re_response_weekly_add": "Successfully added your post to the weekly art submissions! Thank you for participating!",
"py_re_response_weekly_exists": "Couldn't add this post to the submissions! Luckily, it's already there! Thank you for participating!", "py_re_response_weekly_exists": "Couldn't add this post to the submissions! Luckily, it's already there! Thank you for participating!",
"py_re_response_weekly_mod_add": "[MOD ACTION] Successfully added this post to the weekly art submissions!", "py_re_response_weekly_mod_add": "[MOD ACTION] Successfully added this post to the weekly art submissions!",
"py_re_response_weekly_mod_unremove": "[MOD ACTION] Successfully un-removed this post from the weekly art submissions!" "py_re_response_weekly_mod_unremove": "[MOD ACTION] Successfully un-removed this post from the weekly art submissions!",
"success": "Success!"
} }
+4 -1
View File
@@ -19,6 +19,8 @@
"dc_msg_err_trace": "Erreur inconnue !\nTrace de lerreur : {0}", "dc_msg_err_trace": "Erreur inconnue !\nTrace de lerreur : {0}",
"dc_msg_failed_shorturl_conversion": "Échec de la conversion en shortURL : format dURL Reddit invalide.", "dc_msg_failed_shorturl_conversion": "Échec de la conversion en shortURL : format dURL Reddit invalide.",
"dc_msg_mandatory_response": "Message de réponse obligatoire, merci dignorer.", "dc_msg_mandatory_response": "Message de réponse obligatoire, merci dignorer.",
"dc_msg_no_perms": "Permission manquante sur ce serveur : {0}",
"dc_msg_not_in_guild": "Je ne fais pas partie de ce serveur.",
"dc_msg_owner_data_save_complete": "Sauvegarde des données... Terminé !\nFermeture...", "dc_msg_owner_data_save_complete": "Sauvegarde des données... Terminé !\nFermeture...",
"dc_msg_owner_data_save": "Sauvegarde des données...", "dc_msg_owner_data_save": "Sauvegarde des données...",
"dc_msg_owner_shutdown_failed_confirmation": "Échec de larrêt : confirmation invalide.", "dc_msg_owner_shutdown_failed_confirmation": "Échec de larrêt : confirmation invalide.",
@@ -62,5 +64,6 @@
"py_re_response_weekly_add": "Post ajouté avec succès aux soumissions hebdomadaires d'art ! Merci pour votre participation !", "py_re_response_weekly_add": "Post ajouté avec succès aux soumissions hebdomadaires d'art ! Merci pour votre participation !",
"py_re_response_weekly_exists": "Impossible dajouter ce post, il est déjà présent ! Merci pour votre participation !", "py_re_response_weekly_exists": "Impossible dajouter ce post, il est déjà présent ! Merci pour votre participation !",
"py_re_response_weekly_mod_add": "[ACTION MOD] Post ajouté avec succès aux soumissions hebdomadaires dart !", "py_re_response_weekly_mod_add": "[ACTION MOD] Post ajouté avec succès aux soumissions hebdomadaires dart !",
"py_re_response_weekly_mod_unremove": "[ACTION MOD] Post restauré avec succès dans les soumissions hebdomadaires dart !" "py_re_response_weekly_mod_unremove": "[ACTION MOD] Post restauré avec succès dans les soumissions hebdomadaires dart !",
"dc_msg_success": "succès !"
} }
-20
View File
@@ -1,20 +0,0 @@
use crate::{messages::send_msg, Context, Error};
#[poise::command(
slash_command,
prefix_command,
rename = "ping",
category = "fun",
required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL"
)]
/// Check if you have connection to the bot.
pub async fn cmd(
ctx: Context<'_>,
#[description = "The text to echo back."] text: Option<String>,
) -> Result<(), Error>
{
send_msg(ctx, text.unwrap_or_else(|| "Pong".to_string()), true, true).await;
return Ok(());
}
+35
View File
@@ -0,0 +1,35 @@
use poise::serenity_prelude::{CreateInvite, GuildId};
use crate::{Context, Error, lang, messages::send_msg};
pub async fn cmd(ctx: Context<'_>, guild_id: u64) -> Result<(), Error> {
let id = GuildId::from(guild_id);
let guild = id.to_partial_guild(ctx.http()).await?;
if !ctx.serenity_context().cache.guilds().contains(&id) {
send_msg(ctx, lang!("dc_msg_not_in_guild"), true, true).await;
return Ok(());
}
let ch = guild.channels(ctx.http()).await?
.values()
.find(|c| c.is_text_based())
.cloned();
if let Some(channel) = ch {
let bot_member = &guild.member(ctx.http(), ctx.framework().bot_id).await?;
let perms = guild.user_permissions_in(&channel, bot_member);
if !perms.create_instant_invite() {
send_msg(ctx, lang!("dc_msg_no_perms", "CreateInvite"), true, true).await;
return Ok(());
}
let inv_builder = CreateInvite::new().max_age(0).max_uses(0);
let inv = channel.id.create_invite(ctx.http(), inv_builder).await?;
send_msg(ctx, inv.url(), true, true).await;
}
return Ok(());
}
+19
View File
@@ -0,0 +1,19 @@
use poise::serenity_prelude::GuildId;
use crate::{Context, Error, lang, messages::send_msg};
pub async fn cmd(ctx: Context<'_>, guild_id: u64) -> Result<(), Error> {
let id = GuildId::from(guild_id);
let guild = id.to_partial_guild(ctx.http()).await?;
if !ctx.serenity_context().cache.guilds().contains(&id) {
send_msg(ctx, lang!("dc_msg_not_in_guild"), true, true).await;
return Ok(());
}
guild.leave(ctx.http()).await?;
send_msg(ctx, lang!("success"), true, true).await;
return Ok(());
}
+45
View File
@@ -0,0 +1,45 @@
use crate::{Context, Error, debug_cmds::{guild_invite, leave_guild, ping, reload_cfg, stop, view_guilds, whoami}};
#[derive(poise::ChoiceParameter, PartialEq)]
pub enum Subcommands {
GuildInvite,
LeaveGuild,
Ping,
ReloadCfg,
Stop,
ViewGuilds,
WhoAmI
}
#[poise::command(
slash_command,
prefix_command,
category = "owner",
rename = "debug",
required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL"
)]
/// Various debug utilities
pub async fn cmd(
ctx: Context<'_>,
#[description = "Subcommand"] subcommand: Subcommands,
string_arg: Option<String>,
u64_arg: Option<String>
) -> Result<(), Error>
{
let u64_arg_u: u64 = u64_arg.unwrap_or("0".to_string()).as_str().parse()?;
match subcommand {
Subcommands::GuildInvite => guild_invite::cmd(ctx, u64_arg_u).await?,
Subcommands::LeaveGuild => leave_guild::cmd(ctx, u64_arg_u).await?,
Subcommands::Ping => ping::cmd(ctx).await?,
Subcommands::ReloadCfg => reload_cfg::cmd(ctx).await?,
Subcommands::Stop => stop::cmd(ctx, string_arg).await?,
Subcommands::ViewGuilds => view_guilds::cmd(ctx).await?,
Subcommands::WhoAmI => whoami::cmd(ctx).await?,
//_ => return Ok(())
}
return Ok(());
}
+8
View File
@@ -0,0 +1,8 @@
use crate::{messages::send_msg, Context, Error};
pub async fn cmd(ctx: Context<'_>) -> Result<(), Error> {
send_msg(ctx, "Pong".to_string(), true, true).await;
return Ok(());
}
@@ -1,19 +1,7 @@
use crate::{data::{self, get_toml_mutex, read_cfg_data}, lang, messages::send_msg, Context, Error}; use crate::{data::{self, get_toml_mutex, read_cfg_data}, lang, messages::send_msg, Context, Error};
#[poise::command( pub async fn cmd(ctx: Context<'_>) -> Result<(), Error> {
slash_command,
prefix_command,
rename = "reload_cfg",
category = "owner",
owners_only,
required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL"
)]
/// Reloads the entire config file.
pub async fn cmd(
ctx: Context<'_>
) -> Result<(), Error>
{
let r = read_cfg_data(ctx.data(), false).await; let r = read_cfg_data(ctx.data(), false).await;
let d = get_toml_mutex(&ctx.data().cfg).await.unwrap(); let d = get_toml_mutex(&ctx.data().cfg).await.unwrap();
+1 -15
View File
@@ -4,21 +4,7 @@ use poise::serenity_prelude::OnlineStatus;
use crate::{data, lang, messages::{edit_reply, send_msg}, websocket::send_cmd_json, Context, Error}; use crate::{data, lang, messages::{edit_reply, send_msg}, websocket::send_cmd_json, Context, Error};
pub async fn cmd(ctx: Context<'_>, confirmation: Option<String>) -> Result<(), Error>{
#[poise::command(
slash_command,
prefix_command,
rename = "stop",
category = "owner",
owners_only,
required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL"
)]
/// I have security measures, even in developer mode. You wont access this without being a bot "owner".
pub async fn cmd(
ctx: Context<'_>,
#[description = "Type \"i want to stop the bot now\" to confirm."] confirmation: Option<String>,
) -> Result<(), Error>
{
let stop_confirm = "i want to stop the bot now".replace(" ", ""); let stop_confirm = "i want to stop the bot now".replace(" ", "");
let confirm_formatted = confirmation.unwrap_or_default().to_lowercase().replace(" ", ""); let confirm_formatted = confirmation.unwrap_or_default().to_lowercase().replace(" ", "");
let should_stop = ctx.data().args.dev || confirm_formatted == stop_confirm; let should_stop = ctx.data().args.dev || confirm_formatted == stop_confirm;
+29
View File
@@ -0,0 +1,29 @@
use crate::{Context, Error, messages::send_msg};
pub async fn cmd(ctx: Context<'_>) -> Result<(), Error> {
let guilds = ctx.cache().guilds();
let mut msgs: Vec<Vec<String>> = vec![vec![]];
let mut char_count: usize = 0;
let mut msg_idx: usize = 0;
for g in guilds {
let name = g.name(ctx.cache()).unwrap_or("[unnamed]".to_string());
let id = g.get();
let msg = format!("**[{}]** {}", id, name);
char_count += msg.len();
if char_count >= 2000 {
msgs.push(Vec::new());
msg_idx += 1;
}
msgs[msg_idx].push(msg);
}
for msg in msgs
{ send_msg(ctx, msg.join("\n"), true, true).await; }
return Ok(());
}
@@ -1,12 +1,5 @@
use crate::{lang, messages::send_msg, Context, Error}; use crate::{lang, messages::send_msg, Context, Error};
#[poise::command(
slash_command,
prefix_command,
rename = "whoami",
category = "help",
required_bot_permissions = "SEND_MESSAGES | VIEW_CHANNEL"
)]
pub async fn cmd(ctx: Context<'_>) -> Result<(), Error> { pub async fn cmd(ctx: Context<'_>) -> Result<(), Error> {
let data = ctx.data(); let data = ctx.data();
let uid: u64 = ctx.author().id.into(); let uid: u64 = ctx.author().id.into();
+25 -1
View File
@@ -9,6 +9,7 @@ use serde_json::{json, Value};
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::process;
pub fn event_handler<'a>( pub fn event_handler<'a>(
ctx: &'a serenity::Context, ctx: &'a serenity::Context,
@@ -35,10 +36,33 @@ async fn on_ready(ctx: &serenity::Context, data_about_bot: &Ready, data: &Data)
); );
let m_data = get_toml_mutex(&data.cfg).await.unwrap(); let m_data = get_toml_mutex(&data.cfg).await.unwrap();
let custom_activity = ActivityData::custom(m_data["general"]["status"].as_str().unwrap()); let status_str: String;
let status = m_data["general"]["status"].as_str().unwrap();
let status_c = m_data["general"]["statusCommitNumber"].as_bool().unwrap();
let status_ec = m_data["general"]["statusExperimentalCommit"].as_bool().unwrap();
if status_c {
let commit_num_r = process::Command::new("git")
.args(["rev-list", "--count", "HEAD"])
.output()
.unwrap();
let commit_num = format!(
"({} #{})",
if status_ec { "Experimental" }
else { "Commit" },
String::from_utf8(commit_num_r.stdout).unwrap()
).replace("\n", "");
status_str = [status, " ", commit_num.as_str().trim()].concat();
}
else { status_str = status.to_string(); }
let custom_activity = ActivityData::custom(status_str.clone());
ctx.online(); ctx.online();
ctx.set_activity(Some(custom_activity)); ctx.set_activity(Some(custom_activity));
rs_println!("Set bot status as: \"{}\"", status_str);
} }
+9 -7
View File
@@ -4,7 +4,7 @@ use poise::serenity_prelude::UserId;
use poise::serenity_prelude as serenity; use poise::serenity_prelude as serenity;
use poise::serenity_prelude::Client; use poise::serenity_prelude::Client;
use crate::{cmds, data::{self, get_toml_mutex}, events, re_cmds, rs_println, Args, Cmd, Data}; use crate::{Args, Cmd, Data, cmds, data::{self, get_toml_mutex}, debug_cmds, events, re_cmds, rs_println};
pub async fn gen_data(args: Args, owners: Vec<u64>) -> Data { pub async fn gen_data(args: Args, owners: Vec<u64>) -> Data {
@@ -51,7 +51,11 @@ 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.clone().into_iter().map(UserId::from).collect(); let own: HashSet<UserId> = data.owners
.clone()
.into_iter()
.filter_map(|i| if i == 0 { None } else { Some(UserId::from(i))})
.collect();
let framework = poise::Framework::builder() let framework = poise::Framework::builder()
.options(poise::FrameworkOptions { .options(poise::FrameworkOptions {
@@ -79,8 +83,6 @@ async fn make_cmd_vec(data: &Data) -> Vec<Cmd> {
let mut cmds = vec![ let mut cmds = vec![
// GENERIC // GENERIC
cmds::help::cmd(), cmds::help::cmd(),
cmds::whoami::cmd(),
cmds::ping::cmd(),
cmds::eight_ball::cmd(), cmds::eight_ball::cmd(),
// REDDIT // REDDIT
re_cmds::add::cmd(), re_cmds::add::cmd(),
@@ -94,11 +96,11 @@ async fn make_cmd_vec(data: &Data) -> Vec<Cmd> {
// [ADMIN / OWNER] // [ADMIN / OWNER]
cmds::embed::cmd(), cmds::embed::cmd(),
cmds::send::cmd(), cmds::send::cmd(),
cmds::stop::cmd(),
cmds::add_server::cmd(), cmds::add_server::cmd(),
cmds::reload_cfg::cmd(),
// REDDIT [ADMIN / OWNER] // REDDIT [ADMIN / OWNER]
re_cmds::admin_bind::cmd() re_cmds::admin_bind::cmd(),
// DEBUG
debug_cmds::main_cmd::cmd()
]; ];
let cfg = get_toml_mutex(&data.cfg).await.unwrap(); let cfg = get_toml_mutex(&data.cfg).await.unwrap();
+11 -4
View File
@@ -1,16 +1,13 @@
#![warn(unused_extern_crates)] #![warn(unused_extern_crates)]
#![allow(clippy::needless_return)] #![allow(clippy::needless_return)]
#![allow(static_mut_refs)]
mod cmds { mod cmds {
pub mod add_server; pub mod add_server;
pub mod eight_ball; pub mod eight_ball;
pub mod embed; pub mod embed;
pub mod help; pub mod help;
pub mod ping;
pub mod reload_cfg;
pub mod send; pub mod send;
pub mod stop;
pub mod whoami;
} }
mod re_cmds { mod re_cmds {
pub mod add; pub mod add;
@@ -24,6 +21,16 @@ mod re_cmds {
pub mod update; pub mod update;
pub mod vote; pub mod vote;
} }
mod debug_cmds {
pub mod guild_invite;
pub mod leave_guild;
pub mod main_cmd;
pub mod stop;
pub mod ping;
pub mod reload_cfg;
pub mod view_guilds;
pub mod whoami;
}
mod events; mod events;
mod messages; mod messages;
mod python; mod python;
+3 -2
View File
@@ -199,7 +199,7 @@ pub async fn http_edit_msg(
} }
pub async fn send_dm(msg: String, args: Args, owners: Vec<u64>) { pub async fn send_dm(msg: String, args: Args, receivers: Vec<u64>) {
let token: String = let token: String =
if !args.test { env::var("ASSISTANT_TOKEN") .expect("Missing ASSISTANT_TOKEN env var!") } 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!") }; else { env::var("ASSISTANT_TOKEN_TEST").expect("Missing ASSISTANT_TOKEN_TEST env var!") };
@@ -208,7 +208,8 @@ pub async fn send_dm(msg: String, args: Args, owners: Vec<u64>) {
let c_msg = CreateMessage::new().content(msg); let c_msg = CreateMessage::new().content(msg);
for uid in owners { for uid in receivers {
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;
} }
+3 -3
View File
@@ -36,10 +36,10 @@ pub async fn start(args: Args) -> PyResult<()> {
let mut traceback: String = String::new(); let mut traceback: String = String::new();
let mut is_error = false; let mut is_error = false;
pyo3::prepare_freethreaded_python(); Python::initialize();
let _ = Python::with_gil(|py| -> Result<(), PyErr> { let _ = Python::attach(|py| -> Result<(), PyErr> {
let syspath = py.import("sys")?.getattr("path")?.downcast_into::<PyList>()?; let syspath = py.import("sys")?.getattr("path")?.cast_into::<PyList>()?;
syspath.insert(0, path)?; syspath.insert(0, path)?;
let empty = CString::new("").unwrap(); let empty = CString::new("").unwrap();