Update template with changes

This commit is contained in:
toast 2024-12-12 03:01:44 +11:00
parent ef52e95e03
commit 05c9bed1f8
27 changed files with 659 additions and 499 deletions

18
Cargo.lock generated
View File

@ -1463,15 +1463,23 @@ name = "rustbot"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"poise", "poise",
"rand", "rustbot_cmds",
"reqwest",
"rustbot_events", "rustbot_events",
"rustbot_lib", "rustbot_lib",
"rustbot_tokens", "rustbot_tokens",
"tokio",
]
[[package]]
name = "rustbot_cmds"
version = "0.1.0"
dependencies = [
"poise",
"rand",
"reqwest",
"rustbot_lib",
"serde", "serde",
"sysinfo", "sysinfo",
"time",
"tokio",
"uptime_lib", "uptime_lib",
] ]
@ -1492,7 +1500,7 @@ dependencies = [
[[package]] [[package]]
name = "rustbot_lib" name = "rustbot_lib"
version = "0.1.19" version = "0.1.0"
dependencies = [ dependencies = [
"cargo_toml", "cargo_toml",
"poise", "poise",

View File

@ -5,6 +5,7 @@ edition = "2021"
[workspace] [workspace]
members = [ members = [
"cmds",
"events", "events",
"jobs", "jobs",
"library", "library",
@ -16,21 +17,21 @@ cargo_toml = "0.21.0"
poise = "0.6.1" poise = "0.6.1"
regex = "1.11.0" regex = "1.11.0"
serde = "1.0.210" serde = "1.0.210"
tokio = { version = "1.40.0", features = ["macros", "signal", "rt-multi-thread"] }
reqwest = { version = "0.12.8", features = ["native-tls-vendored"] }
[dependencies]
rustbot_events = { path = "events" }
rustbot_lib = { path = "library" }
rustbot_tokens = { path = "tsclient" }
poise = { workspace = true }
rand = "0.8.5" rand = "0.8.5"
reqwest = { workspace = true }
serde = { workspace = true }
sysinfo = "0.33.0" sysinfo = "0.33.0"
time = "0.3.36" time = "0.3.36"
tokio = { workspace = true }
uptime_lib = "0.3.1" uptime_lib = "0.3.1"
tokio = { version = "1.40.0", features = ["macros", "signal", "rt-multi-thread"] }
reqwest = { version = "0.12.8", features = ["native-tls-vendored"] }
rustbot_lib = { path = "library" }
[dependencies]
poise = { workspace = true }
rustbot_cmds = { path = "cmds" }
rustbot_events = { path = "events" }
rustbot_lib = { workspace = true }
rustbot_tokens = { path = "tsclient" }
tokio = { workspace = true }
[patch.crates-io] [patch.crates-io]
poise = { git = "https://github.com/serenity-rs/poise", branch = "serenity-next" } poise = { git = "https://github.com/serenity-rs/poise", branch = "serenity-next" }

13
cmds/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "rustbot_cmds"
version = "0.1.0"
edition = "2024"
[dependencies]
poise = { workspace = true }
rand = { workspace = true }
reqwest = { workspace = true }
rustbot_lib = { workspace = true }
serde = { workspace = true }
sysinfo = { workspace = true }
uptime_lib = { workspace = true }

26
cmds/src/dispatch.rs Normal file
View File

@ -0,0 +1,26 @@
mod dev;
mod eightball;
mod ping;
mod uptime;
pub use {
dev::dev,
eightball::eightball,
ping::ping,
uptime::uptime
};
#[macro_export]
macro_rules! collect {
() => {
vec![
// Developer command(s)
$crate::dev(),
// Utility commands
$crate::ping(),
$crate::uptime(),
// Unsorted mess
$crate::eightball(),
]
};
}

View File

@ -1,13 +1,15 @@
use rustbot_lib::{ use {
RustbotContext, poise::{
RustbotResult CreateReply,
}; serenity_prelude::{
use poise::{ ChannelId,
CreateReply, ShardId,
serenity_prelude::{ ShardRunnerInfo
ChannelId, }
ShardId, },
ShardRunnerInfo rustbot_lib::{
RustbotContext,
RustbotResult
} }
}; };
@ -44,9 +46,7 @@ async fn format_shard_info(
interaction_context = "Guild|BotDm|PrivateChannel", interaction_context = "Guild|BotDm|PrivateChannel",
subcommands("deploy", "servers", "shards", "echo") subcommands("deploy", "servers", "shards", "echo")
)] )]
pub async fn dev(_: RustbotContext<'_>) -> RustbotResult<()> { pub async fn dev(_: RustbotContext<'_>) -> RustbotResult<()> { Ok(()) }
Ok(())
}
/// Deploy commands to this guild or globally /// Deploy commands to this guild or globally
#[poise::command(prefix_command)] #[poise::command(prefix_command)]
@ -90,7 +90,8 @@ async fn echo(
ctx: RustbotContext<'_>, ctx: RustbotContext<'_>,
#[description = "Message to be echoed as a bot"] message: String, #[description = "Message to be echoed as a bot"] message: String,
#[description = "Channel to send this to"] #[description = "Channel to send this to"]
#[channel_types("Text", "PublicThread", "PrivateThread")] channel: Option<ChannelId> #[channel_types("Text", "PublicThread", "PrivateThread")]
channel: Option<ChannelId>
) -> RustbotResult<()> { ) -> RustbotResult<()> {
ctx.defer_ephemeral().await?; ctx.defer_ephemeral().await?;
@ -101,18 +102,10 @@ async fn echo(
match ChannelId::new(channel.get()).say(ctx.http(), message).await { match ChannelId::new(channel.get()).say(ctx.http(), message).await {
Ok(_) => { Ok(_) => {
ctx.send( ctx.send(CreateReply::new().content("Sent!").ephemeral(true)).await?;
CreateReply::new()
.content("Sent!")
.ephemeral(true)
).await?;
}, },
Err(y) => { Err(y) => {
ctx.send( ctx.send(CreateReply::new().content(format!("Failed... `{y}`")).ephemeral(true)).await?;
CreateReply::new()
.content(format!("Failed... `{y}`"))
.ephemeral(true)
).await?;
return Ok(()); return Ok(());
} }
} }

276
cmds/src/dispatch/eightball.rs Executable file
View File

@ -0,0 +1,276 @@
use {
poise::{
builtins::paginate,
serenity_prelude::UserId
},
rand::random,
rustbot_lib::{
RustbotContext,
RustbotResult,
config::BINARY_PROPERTIES
}
};
#[derive(poise::ChoiceParameter, Clone)]
enum ResponseMode {
Normal,
Chicken,
#[name = "Chaotic & Unhinged"]
Chaotic
}
/// Ask the Magic 8-Ball a yes/no question and get an unpredictable answer
#[poise::command(
slash_command,
install_context = "Guild|User",
interaction_context = "Guild|BotDm|PrivateChannel",
rename = "8ball"
)]
pub async fn eightball(
ctx: RustbotContext<'_>,
#[description = "Your yes/no question"] question: String,
#[description = "Response modes"] mode: Option<ResponseMode>
) -> RustbotResult<()> {
if question.to_ascii_lowercase().contains("niko, show list") {
show_list(ctx, mode.clone().unwrap_or(ResponseMode::Normal)).await?;
return Ok(())
}
let rand_resp = match mode {
Some(ResponseMode::Chicken) => get_random_chicken_response(),
Some(ResponseMode::Chaotic) => get_random_chaotic_response(),
_ => get_random_response()
};
ctx.reply(format!("> {question}\n{rand_resp}")).await?;
Ok(())
}
async fn show_list(
ctx: RustbotContext<'_>,
list_type: ResponseMode
) -> RustbotResult<()> {
if ctx.author().id != UserId::new(BINARY_PROPERTIES.developers[0]) {
ctx
.reply("The list knows you're looking, but it's playing a game of hide and seek. For now, it wins.")
.await?;
return Ok(());
}
let chunks: Vec<String> = match list_type {
ResponseMode::Normal => RESPONSES.chunks(10).map(|chunk| chunk.join("\n\n")).collect(),
ResponseMode::Chicken => CHICKEN_RESPONSES.chunks(10).map(|chunk| chunk.join("\n\n")).collect(),
ResponseMode::Chaotic => CHAOTIC_RESPONSES.chunks(10).map(|chunk| chunk.join("\n\n")).collect()
};
let pages: Vec<&str> = chunks.iter().map(|s| s.as_str()).collect();
paginate(ctx, &pages).await?;
Ok(())
}
const RESPONSES: [&str; 45] = [
"Reply hazy. Look it up on Google.", // no
"Meh — Figure it out yourself.", // no
"I don't know, what do you think?", // no
"Yes.", // yes
"No.", // no
"It is decidedly so", // yes
"Signs point to... maybe... depends on... hold on, let me get my glasses, this is getting pretty tiny... depends on whether you'd be up to \
getting to know your Magic 8-Ball a little better.", // no
"Signs point to... ~~yes~~ no.", // no
"Why do you want to know the answer? It's obviously a yes.", // yes
"Outlook not so good.", // no
"Outlook hazy.", // no
"What are you, stupid?", // no
"How the hell do you not know that?", // no
"Really? Making a decision based on what the plastic 8-Ball says? Jesus...", // no
"Try asking later...", // no
"I don't know, whip out the ouija board and try again?", // no
"The answer is yes.", // yes
"Yes, actually no. Wait, nevermind.", // no
"Maybeee...", // yes
"Definitely!", // yes
"It is decidedly so.", // yes
"My reply is no.", // no
"My sources confirms that the answer is no.\nSource: :sparkles: *i made it up* :sparkles:", // no
"As I see it, yes.", // yes
"Don't count on it.", // no
"Whoa! Why do I have to answer this?", // no
"Highly unlikely.", // no
"Sure, but with extreme cautions.", // yes
"What kind of stupid question is that?? No! I'm not answering that!", // no
"Try asking this to a chicken. Probably knows it better than I do!", // no
"Not in a million years!", // no
"As a matter of fact, yes.", // yes
"It's a no, better go ask someone else.", // no
"In the end, it's not a bad choice.", // yes
"Nope, not today.", // no
"Cross your fingers, the answer is yes!", // yes
"Nope. *shakes head*", // no
"The fortune cookie said yes.", // yes
"Sorry, the fortune cookie over there said no.", // no
"Sorry, not happening.", // no
"I'll have to consult my sources... *flips coin*... no.", // no
"I'll have to consult the magic 8-ball... *shakes*... no.", // no
"I'm not sure to be honest, let's ask your friend. Oh wait...", // no
"This question flew over my head, I'll pass.", // no
"Oops, the Magic 8-Ball shattered itself when you asked that! I'll take that as a no." // no
];
const CHICKEN_RESPONSES: [&str; 54] = [
"Cluck cluck... Reply hazy, try pecking Google.", // no
"Meh... Figure it out yourself, or scratch around a bit.", // no
"I don't know... what do you think? *pecks at ground*", // no
"BAWK! YES!", // yes
"Cluck... no.", // no
"It is decidedly so! *flaps wings*", // yes
"Signs point to... maybe... hold on, let me fluff my feathers... depends on whether you'd get to know your Magic Chicken a bit better.", // no
"Signs point to... ~~yes~~ cluck no.", // no
"Why do you want to know? It's a big cluckin' yes!", // yes
"Outlook not so clucking good.", // no
"Outlook cluckin' hazy.", // no
"What are you, a lost chick? Cluck!", // no
"How the cluck do you not know that?", // no
"Really? Asking a chicken to decide your fate? *clucks judgmentally*", // no
"Peck back later, I'm nesting...", // no
"I don't know, try flapping your wings and ask again?", // no
"The answer is a big ol' yes! *flaps happily*", // yes
"Yes... wait, actually... no. Cluck, I'm confused.", // no
"Maaaaybe... *chicken waddle*?", // yes
"Definitely! *struts confidently*", // yes
"It is decidedly so. *struts with pride*", // yes
"My reply is a solid *cluck* no.", // no
"My sources confirm it's a cluckin' no.\nSource: 🐔 *I made it up* 🐔", // no
"As I see it, yes! *pecks approvingly*", // yes
"Don't count on it. *cluck cluck*", // no
"Whoa, why do I have to answer this? *fluffs feathers*", // no
"Highly unlikely. *chicken stare*", // no
"Sure, but with extreme cluckin' caution.", // yes
"What kind of stupid question is that?? No! *angry clucks*", // no
"Try asking this to a fellow chicken. They probably know better than I do!", // no
"Cluck yes! *does a happy chicken dance*", // yes
"No way, not even for a big bag of feed.", // no
"Yes! *lays egg of approval*", // yes
"It's a no, better go scratch somewhere else.", // no
"Cluck-tastic! That's a definite yes.", // yes
"Cluck yeah! *struts proudly*", // yes
"Nope, not today. *shakes head*", // no
"Feathers crossed, the answer is yes!", // yes
"Chicken says nope. *tilts head*", // no
"Absolutely! *clucks happily*", // yes
"Not a chance. *fluffs feathers*", // no
"Eggcellent choice! Yes!", // yes
"Not in a million clucks!", // no
"As a matter of cluck, yes! *clucks approvingly*", // yes
"It's a nopity nope, better go ask another chicken.", // no
"In the end, it's not a bad cluck", // yes
"Nope, not today. *clucks sadly*", // no
"Cross your feathers, the answer is yes!", // yes
"The fortune cookie said yes. *clucks in agreement*", // yes
"Sorry, the fortune cookie over there said no. *clucks in disagreement*", // no
"I'll have to consult my sources... *flips corn*... no.", // no
"I'll have to consult the magic 8-cluck... *shakes*... no.", // no
"I'm not sure to be honest, let's ask your chicken friend. Oh wait...", // no
"This question floated over my head, I'll pass. *clucks dismissively*" // no
];
const CHAOTIC_RESPONSES: [&str; 90] = [
"Oops! The Magic 8-Ball shattered upon hearing your question. Coincidence?", // no
"Reply hazy. Ask Googles evil twin, Froogle.", // no
"Meh — Consult the ancient texts of Netflix subtitles.", // no
"I don't know, but your cat probably does.", // no
"Yes, but only if you wear a clown wig.", // yes
"No. Unless the moon winks at you first.", // no
"It is decidedly a resounding honk-honk!", // yes
"Signs point to... maybe... or not... or wait... oh look, a squirrel!", // no
"Signs point to... ~~yes~~ pancakes. Definitely pancakes.", // no
"Why do you want to know? Its obviously a yes — trust the donut prophecy.", // yes
"Outlook not so good. Blame Mercury retrograde or your Wi-Fi.", // no
"Outlook hazy. Consult the nearest fortune-telling hamster.", // no
"What are you, a toaster in disguise?", // no
"How the heck do you not know this? Ask a sock puppet!", // no
"Really? Making life choices based on a magic ball? Bold move, friend.", // no
"Try asking later... when Im less busy binge-watching.", // no
"I don't know, summon a raven and whisper your question into the void.", // no
"The answer is yes, as foretold by the mystical spaghetti.", // yes
"Yes, actually no. Wait, yes? Lets go with potato.", // no
"Maybeee... if the stars align and your pizza has extra cheese.", // yes
"Definitely! Unless gravity stops working.", // yes
"It is decidedly so. So what? Buy a llama and see what happens.", // yes
"My reply is no, and also banana pudding.", // no
"My sources confirm that the answer is no.\nSource: A suspicious pigeon.", // no
"As I see it, yes. As the chicken sees it, no. Trust who you like.", // yes
"Don't count on it. Count on marshmallows instead.", // no
"Whoa! Why do I have to answer this? Ask a rubber duck.", // no
"Highly unlikely. Unless its Tuesday on Mars.", // no
"Sure, but with extreme caution and a tinfoil hat.", // yes
"What kind of silly question is that?? No! Also, heres a kazoo.", // no
"Try asking this to a chicken. Theyre the true oracles.", // no
"Not in a million years! Unless the earth is made of cheese.", // no
"As a matter of fact, yes. And its raining tacos.", // yes
"It's a no, but the raccoons might know better.", // no
"In the end, its not a bad choice. Or is it? Mwahaha.", // yes
"Nope, not today. Try tomorrow after coffee.", // no
"Cross your fingers! Or better yet, cross the streams.", // yes
"Nope. *shakes head like a very judgmental parrot*", // no
"The fortune cookie said yes, but it was written in crayon.", // yes
"Sorry, the fortune cookie over there said no. Blame it.", // no
"Sorry, not happening. But you get a virtual sticker for trying!", // no
"I'll have to consult my sources... *flips a pancake*... no.", // no
"I'll have to consult the magic 8-ball... *shakes it violently*... still no.", // no
"I'm not sure, but your imaginary friend says yes.", // yes
"This question flew over my head, so Ill just say 'llama'.", // no
"The answer is yes, but only if you do it while wearing socks on your hands.", // yes
"No, and I think you broke the space-time continuum by asking.", // no
"Why not? Whats the worst that could happen? Oh wait...", // no
"The stars say yes, but the planets are still debating.", // yes
"The universe just facepalmed at your question.", // no
"Ask again while juggling flaming pineapples for a clearer answer.", // no
"Nope, not unless you bribe me with tacos.", // no
"I consulted the oracle... shes out to lunch. Try later.", // no
"Yes, but only if you can lick your elbow right now.", // yes
"No, because I said so and Im very wise. Also, Im a plastic ball.", // no
"Yes. No. Wait, Ive lost track. Did you hear that noise?", // no
"Absolutely, as long as you bring me a rubber chicken as tribute.", // yes
"I asked a wizard, and they just laughed hysterically.", // no
"The spirits say no, but the ghosts are nodding yes.", // no
"Yes, if you believe in unicorns and the power of friendship.", // yes
"No, and also you might want to move. Somethings behind you.", // no
"Ask again, but this time with interpretive dance.", // no
"Definitely! Unless the moon turns into cheese. Then no.", // yes
"I see... wait, no, I dont see. My crystal ball is buffering.", // no
"Sure! But only after a karaoke duet with a raccoon.", // yes
"Yes, but only if you promise not to tell the ducks.", // yes
"No way, unless you can recite the alphabet backwards in one breath.", // no
"Ask the magic mushroom. Its way more in touch with reality than I am.", // no
"No, because gravity disagrees with your premise.", // no
"Yes, but first you must complete the sacred quest for nachos.", // yes
"The answer is hidden in the folds of your laundry. Go check.", // no
"I would answer, but Im legally obligated to stay mysterious.", // no
"Absolutely! If you can solve this riddle: What walks on four legs in the morning, two legs at noon, and... oh wait, wrong universe.", // yes
"The council of frogs says yes, but only if you croak like one.", // yes
"No, but only because the Magic 8-Ball union forbids it.", // no
"Yes, if the dog wags its tail twice before the clock strikes midnight.", // yes
"Try again after doing three cartwheels and making a wish.", // no
"The ducks in my dreams say no. Theyre rarely wrong.", // no
"Not today, Satan. Not today.", // no
"Yes, but only on Wednesdays during a full moon.", // yes
"No, because bananas dont grow in winter.", // no
"The answer is locked in a time capsule. Check back in 50 years.", // no
"I dont know, but it smells like trouble.", // no
"Why not? The penguins approve, and thats good enough for me.", // yes
"Sure, but only if you say 'bubblegum' ten times fast.", // yes
"No, unless you can outsmart a sentient toaster.", // no
"The answer is yes, but it comes with a plot twist.", // yes
"Flip a coin, spin three times, and consult your nearest cactus. Good luck!", // no
"Only on the condition that you buy me a donut.", // yes
"Yes, but proceed at your own risk. The llamas are watching." // yes
];
fn get_random_response() -> &'static str { RESPONSES[random::<usize>() % RESPONSES.len()] }
fn get_random_chicken_response() -> &'static str { CHICKEN_RESPONSES[random::<usize>() % CHICKEN_RESPONSES.len()] }
fn get_random_chaotic_response() -> &'static str { CHAOTIC_RESPONSES[random::<usize>() % CHAOTIC_RESPONSES.len()] }

View File

@ -1,7 +1,9 @@
use serde::Deserialize; use {
use rustbot_lib::{ rustbot_lib::{
RustbotContext, RustbotContext,
RustbotResult RustbotResult
},
serde::Deserialize
}; };
#[derive(Deserialize)] #[derive(Deserialize)]
@ -20,16 +22,14 @@ struct Summary {
} }
/// Check latency between bot and WebSocket as well as Discord's API latency /// Check latency between bot and WebSocket as well as Discord's API latency
#[poise::command( #[poise::command(slash_command, install_context = "Guild|User", interaction_context = "Guild|BotDm|PrivateChannel")]
slash_command,
install_context = "Guild|User",
interaction_context = "Guild|BotDm|PrivateChannel"
)]
pub async fn ping(ctx: RustbotContext<'_>) -> RustbotResult<()> { pub async fn ping(ctx: RustbotContext<'_>) -> RustbotResult<()> {
let statuspage: StatusPage = reqwest::get("https://discordstatus.com/metrics-display/5k2rt9f7pmny/day.json") let statuspage: StatusPage = reqwest::get("https://discordstatus.com/metrics-display/5k2rt9f7pmny/day.json")
.await.unwrap() .await
.json() .unwrap()
.await.unwrap(); .json()
.await
.unwrap();
let mut latencies = Vec::new(); let mut latencies = Vec::new();
latencies.push(format!("Discord: `{:.0?}ms`", statuspage.metrics[0].summary.mean)); latencies.push(format!("Discord: `{:.0?}ms`", statuspage.metrics[0].summary.mean));

View File

@ -1,28 +1,31 @@
use sysinfo::System; use {
use uptime_lib::get; rustbot_lib::{
use std::{ RustbotContext,
env::var, RustbotResult,
fs::File, config::BINARY_PROPERTIES,
path::Path, utils::{
time::{ BOT_VERSION,
Duration, GIT_COMMIT_BRANCH,
SystemTime, GIT_COMMIT_HASH,
UNIX_EPOCH format_duration
}
}, },
io::{ std::{
BufRead, env::var,
BufReader fs::File,
} io::{
}; BufRead,
use rustbot_lib::{ BufReader
RustbotContext, },
RustbotResult, path::Path,
utils::{ time::{
BOT_VERSION, Duration,
GIT_COMMIT_HASH, SystemTime,
GIT_COMMIT_BRANCH, UNIX_EPOCH
format_duration }
} },
sysinfo::System,
uptime_lib::get
}; };
fn get_os_info() -> String { fn get_os_info() -> String {
@ -33,13 +36,11 @@ fn get_os_info() -> String {
if let Ok(file) = File::open(path) { if let Ok(file) = File::open(path) {
let reader = BufReader::new(file); let reader = BufReader::new(file);
let set_value = |s: String| s.split('=').nth(1).unwrap_or_default().trim_matches('"').to_string(); let set_value = |s: String| s.split('=').nth(1).unwrap_or_default().trim_matches('"').to_string();
reader.lines().map_while(Result::ok).for_each(|line| { reader.lines().map_while(Result::ok).for_each(|line| match line {
match line { l if l.starts_with("NAME=") => name = set_value(l),
l if l.starts_with("NAME=") => name = set_value(l), l if l.starts_with("VERSION=") => version = set_value(l),
l if l.starts_with("VERSION=") => version = set_value(l), l if l.starts_with("VERSION_ID=") => version = set_value(l),
l if l.starts_with("VERSION_ID=") => version = set_value(l), _ => {}
_ => {}
}
}); });
} }
@ -95,16 +96,21 @@ pub async fn uptime(ctx: RustbotContext<'_>) -> RustbotResult<()> {
} }
// Fetch the node hostname from envvar // Fetch the node hostname from envvar
let docker_node = match var("DOCKER_HOSTNAME") { let node_hostname = if BINARY_PROPERTIES.env.contains("prod") {
Ok(h) => h.to_string(), match var("DOCKER_HOSTNAME") {
Err(_) => "DOCKER_HOSTNAME is empty!".to_string() Ok(h) => h.to_string(),
Err(_) => "DOCKER_HOSTNAME is empty!".to_string()
}
} else {
let hostname = std::process::Command::new("hostname").output().unwrap().stdout;
String::from_utf8(hostname).unwrap().trim().to_string()
}; };
let stat_msg = [ let stat_msg = [
format!("**{} v{}** `{GIT_COMMIT_HASH}:{GIT_COMMIT_BRANCH}`", bot.name, *BOT_VERSION), format!("**{} v{}** `{GIT_COMMIT_HASH}:{GIT_COMMIT_BRANCH}`", bot.name, *BOT_VERSION),
format!(">>> System: `{}`", format_duration(sys_uptime)), format!(">>> System: `{}`", format_duration(sys_uptime)),
format!("Process: `{}`", format_duration(proc_uptime)), format!("Process: `{}`", format_duration(proc_uptime)),
format!("Node: `{docker_node}`"), format!("Node: `{node_hostname}`"),
format!("CPU: `{}`", cpu[0].brand()), format!("CPU: `{}`", cpu[0].brand()),
format!("RAM: `{pram}` (`{sram}/{sram_total}`)"), format!("RAM: `{pram}` (`{sram}/{sram_total}`)"),
format!("OS: `{}`", get_os_info()) format!("OS: `{}`", get_os_info())

2
cmds/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
mod dispatch;
pub use dispatch::*;

View File

@ -4,8 +4,8 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
rustbot_lib = { path = "../library" }
poise = { workspace = true } poise = { workspace = true }
rustbot_lib = { workspace = true }
[features] [features]
production = ["rustbot_lib/production"] production = ["rustbot_lib/production"]

View File

@ -1,10 +1,12 @@
mod ready; mod ready;
mod shards; mod shards;
use poise::serenity_prelude::FullEvent; use {
use rustbot_lib::{ poise::serenity_prelude::FullEvent,
RustbotFwCtx, rustbot_lib::{
RustbotResult RustbotFwCtx,
RustbotResult
}
}; };
pub const RUSTBOT_EVENT: &str = "RustbotEvent"; pub const RUSTBOT_EVENT: &str = "RustbotEvent";

View File

@ -3,26 +3,28 @@ use super::{
RUSTBOT_EVENT RUSTBOT_EVENT
}; };
use rustbot_lib::{ use {
RustbotFwCtx, poise::serenity_prelude::{
RustbotResult, ChannelId,
utils::{ CreateEmbed,
BOT_VERSION, CreateEmbedAuthor,
GIT_COMMIT_HASH, CreateMessage,
GIT_COMMIT_BRANCH Ready
}, },
config::BINARY_PROPERTIES rustbot_lib::{
}; config::BINARY_PROPERTIES,
use std::sync::atomic::{ utils::{
AtomicBool, BOT_VERSION,
Ordering::Relaxed GIT_COMMIT_BRANCH,
}; GIT_COMMIT_HASH
use poise::serenity_prelude::{ },
Ready, RustbotFwCtx,
ChannelId, RustbotResult
CreateMessage, },
CreateEmbed, std::sync::atomic::{
CreateEmbedAuthor AtomicBool,
Ordering::Relaxed
}
}; };
static READY_ONCE: AtomicBool = AtomicBool::new(false); static READY_ONCE: AtomicBool = AtomicBool::new(false);
@ -33,14 +35,26 @@ async fn ready_once(
) -> RustbotResult<()> { ) -> RustbotResult<()> {
#[cfg(not(feature = "production"))] #[cfg(not(feature = "production"))]
{ {
println!("{RUSTBOT_EVENT}[Ready:Notice:S{}]: Detected a non-production environment!", framework.serenity_context.shard_id); println!(
"{RUSTBOT_EVENT}[Ready:Notice:S{}]: Detected a non-production environment!",
framework.serenity_context.shard_id
);
let gateway = framework.serenity_context.http.get_bot_gateway().await?; let gateway = framework.serenity_context.http.get_bot_gateway().await?;
let session = gateway.session_start_limit; let session = gateway.session_start_limit;
println!("{RUSTBOT_EVENT}[Ready:Notice:S{}]: Session limit: {}/{}", framework.serenity_context.shard_id, session.remaining, session.total); println!(
"{RUSTBOT_EVENT}[Ready:Notice:S{}]: Session limit: {}/{}",
framework.serenity_context.shard_id, session.remaining, session.total
);
} }
println!("{RUSTBOT_EVENT}[Ready:S{}]: Build version: {} ({}:{})", framework.serenity_context.shard_id, *BOT_VERSION, GIT_COMMIT_HASH, GIT_COMMIT_BRANCH); println!(
println!("{RUSTBOT_EVENT}[Ready:S{}]: Connected to API as {}", framework.serenity_context.shard_id, ready.user.name); "{RUSTBOT_EVENT}[Ready:S{}]: Build version: {} ({GIT_COMMIT_HASH}:{GIT_COMMIT_BRANCH})",
framework.serenity_context.shard_id, *BOT_VERSION
);
println!(
"{RUSTBOT_EVENT}[Ready:S{}]: Connected to API as {}",
framework.serenity_context.shard_id, ready.user.name
);
let message = CreateMessage::new(); let message = CreateMessage::new();
let ready_embed = CreateEmbed::new() let ready_embed = CreateEmbed::new()
@ -48,7 +62,9 @@ async fn ready_once(
.thumbnail(ready.user.avatar_url().unwrap_or_default()) .thumbnail(ready.user.avatar_url().unwrap_or_default())
.author(CreateEmbedAuthor::new(format!("{} is ready!", ready.user.name))); .author(CreateEmbedAuthor::new(format!("{} is ready!", ready.user.name)));
ChannelId::new(BINARY_PROPERTIES.rustbot_logs).send_message(&framework.serenity_context.http, message.add_embed(ready_embed)).await?; ChannelId::new(BINARY_PROPERTIES.rustbot_logs)
.send_message(&framework.serenity_context.http, message.add_embed(ready_embed))
.await?;
Ok(()) Ok(())
} }
@ -59,7 +75,9 @@ impl EventProcessor<'_> {
data_about_bot: &Ready data_about_bot: &Ready
) -> RustbotResult<()> { ) -> RustbotResult<()> {
if !READY_ONCE.swap(true, Relaxed) { if !READY_ONCE.swap(true, Relaxed) {
ready_once(data_about_bot, self.framework).await.expect("Failed to call ready_once method"); ready_once(data_about_bot, self.framework)
.await
.expect("Failed to call ready_once method");
} }
Ok(()) Ok(())

View File

@ -3,16 +3,22 @@ use super::{
RUSTBOT_EVENT RUSTBOT_EVENT
}; };
use std::num::NonZero; use {
use rustbot_lib::RustbotResult; poise::serenity_prelude::ShardStageUpdateEvent,
use poise::serenity_prelude::ShardStageUpdateEvent; rustbot_lib::RustbotResult,
std::num::NonZero
};
impl EventProcessor<'_> { impl EventProcessor<'_> {
pub async fn on_shards_ready( pub async fn on_shards_ready(
&self, &self,
total_shards: &NonZero<u16> total_shards: &NonZero<u16>
) -> RustbotResult<()> { ) -> RustbotResult<()> {
let shards = if *total_shards == NonZero::new(1).unwrap() { "shard is" } else { "shards are" }; let shards = if *total_shards == NonZero::new(1).unwrap() {
"shard is"
} else {
"shards are"
};
println!("{RUSTBOT_EVENT}[ShardsReady]: {total_shards} {shards} ready!"); println!("{RUSTBOT_EVENT}[ShardsReady]: {total_shards} {shards} ready!");
Ok(()) Ok(())

View File

@ -1,23 +1,23 @@
use crate::RUSTBOT_SCHEDULER; use crate::RUSTBOT_SCHEDULER;
use tokio::{ use {
task, std::{
time::{ future::Future,
interval, sync::Arc
Duration },
tokio::{
task,
time::{
interval,
Duration
}
} }
}; };
use std::{
sync::Arc,
future::Future
};
pub struct Scheduler; pub struct Scheduler;
impl Scheduler { impl Scheduler {
pub fn new() -> Arc<Self> { pub fn new() -> Arc<Self> { Arc::new(Self) }
Arc::new(Self)
}
pub async fn spawn_job<F, E>( pub async fn spawn_job<F, E>(
&self, &self,

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rustbot_lib" name = "rustbot_lib"
version = "0.1.19" version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View File

@ -23,15 +23,4 @@ fn main() {
println!("cargo:rustc-env=GIT_COMMIT_BRANCH=not_found"); println!("cargo:rustc-env=GIT_COMMIT_BRANCH=not_found");
} }
} }
{
let hostname = std::process::Command::new("hostname")
.output()
.expect("Command execution failed: hostname");
if hostname.status.success() {
let hostname = String::from_utf8(hostname.stdout).expect("Invalid UTF-8 sequence").trim().to_string();
println!("cargo:rustc-env=DOCKER_HOSTNAME={}", &hostname);
}
}
} }

View File

@ -1,43 +1,45 @@
use std::sync::LazyLock; use std::sync::LazyLock;
pub struct ConfigMeta { pub struct ConfigMeta {
pub env: &'static str, pub env: &'static str,
pub embed_color: u32, pub embed_color: u32,
pub rustbot_logs: u64, pub rustbot_logs: u64,
pub developers: Vec<u64> pub developers: Vec<u64>
} }
#[cfg(feature = "production")] #[cfg(feature = "production")]
pub static BINARY_PROPERTIES: LazyLock<ConfigMeta> = LazyLock::new(ConfigMeta::new); pub static BINARY_PROPERTIES: LazyLock<ConfigMeta> = LazyLock::new(ConfigMeta::new);
#[cfg(not(feature = "production"))] #[cfg(not(feature = "production"))]
pub static BINARY_PROPERTIES: LazyLock<ConfigMeta> = LazyLock::new(|| pub static BINARY_PROPERTIES: LazyLock<ConfigMeta> = LazyLock::new(|| ConfigMeta::new().env("dev").embed_color(0xF1D63C));
ConfigMeta::new()
.env("dev")
.embed_color(0xf1d63c)
);
impl ConfigMeta { impl ConfigMeta {
fn new() -> Self { fn new() -> Self {
Self { Self {
env: "prod", env: "prod",
embed_color: 0xf1d63c, embed_color: 0xF1D63C,
rustbot_logs: 1311282815601741844, rustbot_logs: 1311282815601741844,
developers: vec![ developers: vec![
190407856527376384 // toast.ts 190407856527376384, // toast.ts
] ]
} }
} }
// Scalable functions below; // Scalable functions below;
#[cfg(not(feature = "production"))] #[cfg(not(feature = "production"))]
fn env(mut self, env: &'static str) -> Self { fn env(
mut self,
env: &'static str
) -> Self {
self.env = env; self.env = env;
self self
} }
#[cfg(not(feature = "production"))] #[cfg(not(feature = "production"))]
fn embed_color(mut self, color: u32) -> Self { fn embed_color(
mut self,
color: u32
) -> Self {
self.embed_color = color; self.embed_color = color;
self self
} }

View File

@ -1,6 +1,8 @@
use poise::serenity_prelude::UserId; use {
use cargo_toml::Manifest; cargo_toml::Manifest,
use std::sync::LazyLock; poise::serenity_prelude::UserId,
std::sync::LazyLock
};
#[cfg(feature = "production")] #[cfg(feature = "production")]
pub static GIT_COMMIT_HASH: &str = env!("GIT_COMMIT_HASH"); pub static GIT_COMMIT_HASH: &str = env!("GIT_COMMIT_HASH");
@ -17,9 +19,7 @@ pub static BOT_VERSION: LazyLock<String> = LazyLock::new(|| {
.unwrap() .unwrap()
}); });
pub fn format_timestamp(timestamp: i64) -> String { pub fn format_timestamp(timestamp: i64) -> String { format!("<t:{timestamp}>\n<t:{timestamp}:R>") }
format!("<t:{timestamp}>\n<t:{timestamp}:R>")
}
pub fn mention_dev(ctx: super::RustbotContext<'_>) -> Option<String> { pub fn mention_dev(ctx: super::RustbotContext<'_>) -> Option<String> {
let devs = super::config::BINARY_PROPERTIES.developers.clone(); let devs = super::config::BINARY_PROPERTIES.developers.clone();
@ -53,18 +53,13 @@ pub fn format_duration(secs: u64) -> String {
let minutes = (secs % 3600) / 60; let minutes = (secs % 3600) / 60;
let seconds = secs % 60; let seconds = secs % 60;
let components = [ let components = [(days, "d"), (hours, "h"), (minutes, "m"), (seconds, "s")];
(days, "d"),
(hours, "h"),
(minutes, "m"),
(seconds, "s"),
];
let formatted_string: Vec<String> = components let formatted_string: Vec<String> = components
.iter() .iter()
.filter(|&&(value, _)| value > 0) .filter(|&&(value, _)| value > 0)
.map(|&(value, suffix)| format!("{value}{suffix}")) .map(|&(value, suffix)| format!("{value}{suffix}"))
.collect(); .collect();
formatted_string.join(", ") formatted_string.join(", ")
} }

4
run.sh
View File

@ -1,6 +1,4 @@
#!/bin/bash #!/bin/bash
export DOCKER_HOSTNAME=$(hostname)
export $(cat .env.bot | xargs) export $(cat .env.bot | xargs)
clear && cargo run clear && cargo fmt && cargo run
unset DOCKER_HOSTNAME

2
rust-toolchain Normal file
View File

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

20
rustfmt.toml Normal file
View File

@ -0,0 +1,20 @@
edition = "2024"
hex_literal_case = "Upper"
binop_separator = "Front"
brace_style = "SameLineWhere"
fn_params_layout = "Vertical"
imports_layout = "Vertical"
imports_granularity = "One"
fn_single_line = true
format_strings = true
max_width = 150
tab_spaces = 2
hard_tabs = false
trailing_comma = "Never"
match_block_trailing_comma = true
reorder_imports = true
reorder_modules = true
reorder_impl_items = true
trailing_semicolon = false
struct_field_align_threshold = 20
condense_wildcard_suffixes = true

View File

@ -1,26 +0,0 @@
mod dev;
mod eightball;
mod ping;
mod uptime;
pub use dev::dev;
pub use eightball::eightball;
pub use ping::ping;
pub use uptime::uptime;
macro_rules! collect {
() => {
vec![
// Developer command(s)
commands::dev(),
// Utility commands
commands::ping(),
commands::uptime(),
// Unsorted mess
commands::eightball(),
]
};
}
pub(crate) use collect;

View File

@ -1,180 +0,0 @@
use rustbot_lib::{
RustbotContext,
RustbotResult,
config::BINARY_PROPERTIES
};
use poise::{
serenity_prelude::UserId,
builtins::paginate
};
#[derive(poise::ChoiceParameter)]
enum ResponseMode {
Normal,
Chicken
}
/// Ask the Magic 8-Ball a yes/no question and get an unpredictable answer
#[poise::command(
slash_command,
install_context = "Guild|User",
interaction_context = "Guild|BotDm|PrivateChannel",
rename = "8ball"
)]
pub async fn eightball(
ctx: RustbotContext<'_>,
#[description = "Your yes/no question"] question: String,
#[description = "Response modes"] mode: Option<ResponseMode>
) -> RustbotResult<()> {
if question.to_ascii_lowercase().contains("niko, show list") {
if ctx.author().id == UserId::new(BINARY_PROPERTIES.developers[0]) {
let chunks: Vec<String> = RESPONSES.chunks(10).map(|chunk| chunk.join("\n\n")).collect();
let pages: Vec<&str> = chunks.iter().map(|s| s.as_str()).collect();
paginate(ctx, &pages).await?;
return Ok(());
} else {
ctx.reply("No.").await?;
return Ok(());
}
}
if question.to_ascii_lowercase().contains("niko, show chicken list") {
if ctx.author().id == UserId::new(BINARY_PROPERTIES.developers[0]) {
let chunks: Vec<String> = CHICKEN_RESPONSES.chunks(10).map(|chunk| chunk.join("\n\n")).collect();
let pages: Vec<&str> = chunks.iter().map(|s| s.as_str()).collect();
paginate(ctx, &pages).await?;
return Ok(());
} else {
ctx.reply("No.").await?;
return Ok(());
}
}
let rand_resp = match mode {
Some(ResponseMode::Chicken) => get_random_chicken_response(),
_ => get_random_response()
};
ctx.reply(format!("> {question}\n{rand_resp}")).await?;
Ok(())
}
const RESPONSES: [&str; 45] = [
"Reply hazy. Look it up on Google.", // no
"Meh — Figure it out yourself.", // no
"I don't know, what do you think?", // no
"Yes.", // yes
"No.", // no
"It is decidedly so", // yes
"Signs point to... maybe... depends on... \
hold on, let me get my glasses, this is getting \
pretty tiny... depends on whether you'd be up \
to getting to know your Magic 8-Ball a little better.", // no
"Signs point to... ~~yes~~ no.", // no
"Why do you want to know the answer? It's obviously a yes.", // yes
"Outlook not so good.", // no
"Outlook hazy.", // no
"What are you, stupid?", // no
"How the hell do you not know that?", // no
"Really? Making a decision based on what the plastic 8-Ball says? Jesus...", // no
"Try asking later...", // no
"I don't know, whip out the ouija board and try again?", // no
"The answer is yes.", // yes
"Yes, actually no. Wait, nevermind.", // no
"Maybeee...", // yes
"Definitely!", // yes
"It is decidedly so.", // yes
"My reply is no.", // no
"My sources confirms that the answer is no.\n\
Source: :sparkles: *i made it up* :sparkles:", // no
"As I see it, yes.", // yes
"Don't count on it.", // no
"Whoa! Why do I have to answer this?", // no
"Highly unlikely.", // no
"Sure, but with extreme cautions.", // yes
"What kind of stupid question is that?? No! I'm not answering that!", // no
"Try asking this to a chicken. Probably knows it better than I do!", // no
"Not in a million years!", // no
"As a matter of fact, yes.", // yes
"It's a no, better go ask someone else.", // no
"In the end, it's not a bad choice.", // yes
"Nope, not today.", // no
"Cross your fingers, the answer is yes!", // yes
"Nope. *shakes head*", // no
"The fortune cookie said yes.", // yes
"Sorry, the fortune cookie over there said no.", // no
"Sorry, not happening.", // no
"I'll have to consult my sources... *flips coin*... no.", // no
"I'll have to consult the magic 8-ball... *shakes*... no.", // no
"I'm not sure to be honest, let's ask your friend. Oh wait...", // no
"This question flew over my head, I'll pass.", // no
"Oops, the Magic 8-Ball shattered itself when you asked that! I'll take that as a no.", // no
];
const CHICKEN_RESPONSES: [&str; 54] = [
"Cluck cluck... Reply hazy, try pecking Google.", // no
"Meh... Figure it out yourself, or scratch around a bit.", // no
"I don't know... what do you think? *pecks at ground*", // no
"BAWK! YES!", // yes
"Cluck... no.", // no
"It is decidedly so! *flaps wings*", // yes
"Signs point to... maybe... hold on, let me fluff my feathers... depends on whether you'd get to know your Magic Chicken a bit better.", // no
"Signs point to... ~~yes~~ cluck no.", // no
"Why do you want to know? It's a big cluckin' yes!", // yes
"Outlook not so clucking good.", // no
"Outlook cluckin' hazy.", // no
"What are you, a lost chick? Cluck!", // no
"How the cluck do you not know that?", // no
"Really? Asking a chicken to decide your fate? *clucks judgmentally*", // no
"Peck back later, I'm nesting...", // no
"I don't know, try flapping your wings and ask again?", // no
"The answer is a big ol' yes! *flaps happily*", // yes
"Yes... wait, actually... no. Cluck, I'm confused.", // no
"Maaaaybe... *chicken waddle*?", // yes
"Definitely! *struts confidently*", // yes
"It is decidedly so. *struts with pride*", // yes
"My reply is a solid *cluck* no.", // no
"My sources confirm it's a cluckin' no.\nSource: 🐔 *I made it up* 🐔", // no
"As I see it, yes! *pecks approvingly*", // yes
"Don't count on it. *cluck cluck*", // no
"Whoa, why do I have to answer this? *fluffs feathers*", // no
"Highly unlikely. *chicken stare*", // no
"Sure, but with extreme cluckin' caution.", // yes
"What kind of stupid question is that?? No! *angry clucks*", // no
"Try asking this to a fellow chicken. They probably know better than I do!", // no
"Cluck yes! *does a happy chicken dance*", // yes
"No way, not even for a big bag of feed.", // no
"Yes! *lays egg of approval*", // yes
"It's a no, better go scratch somewhere else.", // no
"Cluck-tastic! That's a definite yes.", // yes
"Cluck yeah! *struts proudly*", // yes
"Nope, not today. *shakes head*", // no
"Feathers crossed, the answer is yes!", // yes
"Chicken says nope. *tilts head*", // no
"Absolutely! *clucks happily*", // yes
"Not a chance. *fluffs feathers*", // no
"Eggcellent choice! Yes!", // yes
"Not in a million clucks!", // no
"As a matter of cluck, yes! *clucks approvingly*", // yes
"It's a nopity nope, better go ask another chicken.", // no
"In the end, it's not a bad cluck", // yes
"Nope, not today. *clucks sadly*", // no
"Cross your feathers, the answer is yes!", // yes
"The fortune cookie said yes. *clucks in agreement*", // yes
"Sorry, the fortune cookie over there said no. *clucks in disagreement*", // no
"I'll have to consult my sources... *flips corn*... no.", // no
"I'll have to consult the magic 8-cluck... *shakes*... no.", // no
"I'm not sure to be honest, let's ask your chicken friend. Oh wait...", // no
"This question floated over my head, I'll pass. *clucks dismissively*", // no
];
fn get_random_response() -> &'static str {
RESPONSES[rand::random::<usize>() % RESPONSES.len()]
}
fn get_random_chicken_response() -> &'static str {
CHICKEN_RESPONSES[rand::random::<usize>() % CHICKEN_RESPONSES.len()]
}

View File

@ -1,27 +1,29 @@
mod commands;
mod shutdown; mod shutdown;
// https://cdn.toast-server.net/RustFSHiearchy.png // https://cdn.toast-server.net/RustFSHiearchy.png
// Using the new filesystem hierarchy // Using the new filesystem hierarchy
use rustbot_tokens::discord_token; use {
use poise::serenity_prelude::{ poise::serenity_prelude::{
builder::CreateAllowedMentions, builder::CreateAllowedMentions,
ClientBuilder, ActivityData,
ActivityData, ClientBuilder,
GatewayIntents GatewayIntents
};
use rustbot_lib::{
utils::{
mention_dev,
get_guild_name
}, },
RustbotData, rustbot_cmds::collect,
config::BINARY_PROPERTIES rustbot_events::events::processor,
}; rustbot_lib::{
use rustbot_events::events::processor; config::BINARY_PROPERTIES,
use std::{ utils::{
sync::Arc, get_guild_name,
borrow::Cow mention_dev
},
RustbotData
},
rustbot_tokens::discord_token,
std::{
borrow::Cow,
sync::Arc
}
}; };
#[tokio::main] #[tokio::main]
@ -32,28 +34,29 @@ async fn main() {
Some(Cow::Borrowed("pg!")) Some(Cow::Borrowed("pg!"))
}; };
let commands = commands::collect!();
let framework = poise::Framework::builder() let framework = poise::Framework::builder()
.options(poise::FrameworkOptions { .options(poise::FrameworkOptions {
commands, commands: collect!(),
pre_command: |ctx| Box::pin(async move { pre_command: |ctx| {
let get_guild_channel_name = match ctx.guild_channel().await { Box::pin(async move {
Some(channel) => format!("in #{}", channel.name.clone()), let get_guild_channel_name = match ctx.guild_channel().await {
None => String::from("") Some(channel) => format!("in #{}", channel.name.clone()),
}; None => String::from("")
let prefix = match ctx.command().prefix_action { };
Some(_) => ctx.framework().options.prefix_options.prefix.as_ref().unwrap(), let prefix = match ctx.command().prefix_action {
None => "/" Some(_) => ctx.framework().options.prefix_options.prefix.as_ref().unwrap(),
}; None => "/"
};
println!( println!(
"Discord[{}:S{}]: {} ran {prefix}{} {get_guild_channel_name}", "Discord[{}:S{}]: {} ran {prefix}{} {get_guild_channel_name}",
get_guild_name(ctx), get_guild_name(ctx),
ctx.serenity_context().shard_id, ctx.serenity_context().shard_id,
ctx.author().name, ctx.author().name,
ctx.command().qualified_name, ctx.command().qualified_name,
); );
}), })
},
prefix_options: poise::PrefixFrameworkOptions { prefix_options: poise::PrefixFrameworkOptions {
prefix, prefix,
ignore_bots: true, ignore_bots: true,
@ -62,52 +65,69 @@ async fn main() {
execute_self_messages: false, execute_self_messages: false,
..Default::default() ..Default::default()
}, },
on_error: |error| Box::pin(async move { on_error: |error| {
match error { Box::pin(async move {
poise::FrameworkError::Command { error, ctx, .. } => { match error {
println!("PoiseCommandError({}): {}", ctx.command().qualified_name, error); poise::FrameworkError::Command { error, ctx, .. } => {
ctx.reply(format!( println!("PoiseCommandError({}): {}", ctx.command().qualified_name, error);
"Encountered an error during command execution, ask {} to check console for more details!", ctx
mention_dev(ctx).unwrap_or_default() .reply(format!(
)).await.expect("Error sending message"); "Encountered an error during command execution, ask {} to check console for more details!",
}, mention_dev(ctx).unwrap_or_default()
poise::FrameworkError::EventHandler { error, event, .. } => println!("PoiseEventHandlerError({}): {}", event.snake_case_name(), error), ))
poise::FrameworkError::NotAnOwner { ctx, .. } => { .await
println!("PoiseNotAnOwner: {} tried to execute a developer-level command ({})", ctx.author().name, ctx.command().qualified_name); .expect("Error sending message");
ctx.reply("Whoa, you discovered a hidden command! Too bad, I can't allow you to execute it as you're not my creator.").await.expect("Error sending message"); },
}, poise::FrameworkError::EventHandler { error, event, .. } => println!("PoiseEventHandlerError({}): {}", event.snake_case_name(), error),
poise::FrameworkError::UnknownInteraction { interaction, .. } => println!( poise::FrameworkError::NotAnOwner { ctx, .. } => {
"PoiseUnknownInteractionError: {} tried to execute an unknown interaction ({})", println!(
interaction.user.name, "PoiseNotAnOwner: {} tried to execute a developer-level command ({})",
interaction.data.name ctx.author().name,
), ctx.command().qualified_name
poise::FrameworkError::UnknownCommand { msg, .. } => println!( );
"PoiseUnknownCommandError: {} tried to execute an unknown command ({})", ctx
msg.author.name, .reply("Whoa, you discovered a hidden command! Too bad, I can't allow you to execute it as you're not my creator.")
msg.content .await
), .expect("Error sending message");
poise::FrameworkError::ArgumentParse { ctx, error, .. } => { },
println!("PoiseArgumentParseError: {}", error); poise::FrameworkError::UnknownInteraction { interaction, .. } => println!(
ctx.reply(format!("Error parsing argument(s): {error}")).await.expect("Error sending message"); "PoiseUnknownInteractionError: {} tried to execute an unknown interaction ({})",
}, interaction.user.name, interaction.data.name
poise::FrameworkError::CommandPanic { ctx, payload, .. } => { ),
if let Some(payload) = payload.clone() { poise::FrameworkError::UnknownCommand { msg, .. } => println!(
println!("PoiseCommandPanic: {payload}"); "PoiseUnknownCommandError: {} tried to execute an unknown command ({})",
ctx.reply(format!( msg.author.name, msg.content
"The command panicked, please tell my developer about this!\n**Error:**```\n{payload}\n```" ),
)).await.expect("Error sending message"); poise::FrameworkError::ArgumentParse { ctx, error, .. } => {
} else { println!("PoiseArgumentParseError: {}", error);
println!("PoiseCommandPanic: No payload provided"); ctx
let uh_oh = [ .reply(format!("Error parsing argument(s): {error}"))
"Well, this is concerning... Hopefully you notified my developer about this!", .await
"The command panicked, but didn't leave any trace behind... Suspicious!", .expect("Error sending message");
].join("\n"); },
ctx.reply(uh_oh).await.expect("Error sending message"); poise::FrameworkError::CommandPanic { ctx, payload, .. } => {
} if let Some(payload) = payload.clone() {
}, println!("PoiseCommandPanic: {payload}");
other => println!("PoiseOtherError: {other}") ctx
} .reply(format!(
}), "The command panicked, please tell my developer about this!\n**Error:**```\n{payload}\n```"
))
.await
.expect("Error sending message");
} else {
println!("PoiseCommandPanic: No payload provided");
let uh_oh = [
"Well, this is concerning... Hopefully you notified my developer about this!",
"The command panicked, but didn't leave any trace behind... Suspicious!"
]
.join("\n");
ctx.reply(uh_oh).await.expect("Error sending message");
}
},
other => println!("PoiseOtherError: {other}")
}
})
},
allowed_mentions: Some(CreateAllowedMentions::default().empty_users()), allowed_mentions: Some(CreateAllowedMentions::default().empty_users()),
initialize_owners: true, initialize_owners: true,
skip_checks_for_owners: true, skip_checks_for_owners: true,
@ -118,14 +138,13 @@ async fn main() {
let mut client = ClientBuilder::new( let mut client = ClientBuilder::new(
discord_token().await, discord_token().await,
GatewayIntents::GUILDS GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT
| GatewayIntents::GUILD_MESSAGES
| GatewayIntents::MESSAGE_CONTENT
) )
.framework(framework) .framework(framework)
.data(Arc::new(RustbotData {})) .data(Arc::new(RustbotData {}))
.activity(ActivityData::custom("nep nep!")) .activity(ActivityData::custom("nep nep!"))
.await.expect("Error creating client"); .await
.expect("Error creating client");
let shard_manager = client.shard_manager.clone(); let shard_manager = client.shard_manager.clone();

View File

@ -7,16 +7,11 @@ use tokio::{
}; };
pub async fn gracefully_shutdown() { pub async fn gracefully_shutdown() {
let [mut s1, mut s2, mut s3] = [ let [mut s1, mut s2] = [signal(SignalKind::interrupt()).unwrap(), signal(SignalKind::hangup()).unwrap()];
signal(SignalKind::interrupt()).unwrap(),
signal(SignalKind::terminate()).unwrap(),
signal(SignalKind::hangup()).unwrap()
];
select!( select!(
v = s1.recv() => v.unwrap(), v = s1.recv() => v.unwrap(),
v = s2.recv() => v.unwrap(), v = s2.recv() => v.unwrap()
v = s3.recv() => v.unwrap()
); );
println!("\nRustbot says goodbye! 👋"); println!("\nRustbot says goodbye! 👋");

View File

@ -5,5 +5,5 @@ edition = "2021"
[dependencies] [dependencies]
poise = { workspace = true } poise = { workspace = true }
tokenservice-client = { version = "0.4.1", registry = "gitea" } tokenservice-client = { version = "0.4.2", registry = "gitea" }
tokio = { workspace = true } tokio = { workspace = true }

View File

@ -1,20 +1,20 @@
use poise::serenity_prelude::Token; use {
use tokio::sync::Mutex; poise::serenity_prelude::Token,
use std::{ std::{
str::FromStr, str::FromStr,
sync::LazyLock sync::LazyLock
}; },
use tokenservice_client::{ tokenservice_client::{
TokenService, TokenService,
TokenServiceApi TokenServiceApi
},
tokio::sync::Mutex
}; };
pub struct TSClient(TokenService); pub struct TSClient(TokenService);
impl Default for TSClient { impl Default for TSClient {
fn default() -> Self { fn default() -> Self { Self::new() }
Self::new()
}
} }
impl TSClient { impl TSClient {
@ -34,11 +34,6 @@ impl TSClient {
static TSCLIENT: LazyLock<Mutex<TSClient>> = LazyLock::new(|| Mutex::new(TSClient::new())); static TSCLIENT: LazyLock<Mutex<TSClient>> = LazyLock::new(|| Mutex::new(TSClient::new()));
pub async fn token_path() -> TokenServiceApi { pub async fn token_path() -> TokenServiceApi { TSCLIENT.lock().await.get().await.unwrap() }
TSCLIENT.lock().await.get().await.unwrap()
}
pub async fn discord_token() -> Token { pub async fn discord_token() -> Token { Token::from_str(&token_path().await.main).expect("Serenity couldn't parse the bot token!") }
Token::from_str(&token_path().await.main)
.expect("Serenity couldn't parse the bot token!")
}