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,14 +1,16 @@
use rustbot_lib::{ use {
RustbotContext, poise::{
RustbotResult
};
use poise::{
CreateReply, CreateReply,
serenity_prelude::{ serenity_prelude::{
ChannelId, ChannelId,
ShardId, ShardId,
ShardRunnerInfo ShardRunnerInfo
} }
},
rustbot_lib::{
RustbotContext,
RustbotResult
}
}; };
async fn format_shard_info( async fn format_shard_info(
@ -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
.unwrap()
.json() .json()
.await.unwrap(); .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,
RustbotResult,
config::BINARY_PROPERTIES,
utils::{
BOT_VERSION,
GIT_COMMIT_BRANCH,
GIT_COMMIT_HASH,
format_duration
}
},
std::{
env::var, env::var,
fs::File, fs::File,
io::{
BufRead,
BufReader
},
path::Path, path::Path,
time::{ time::{
Duration, Duration,
SystemTime, SystemTime,
UNIX_EPOCH UNIX_EPOCH
}
}, },
io::{ sysinfo::System,
BufRead, uptime_lib::get
BufReader
}
};
use rustbot_lib::{
RustbotContext,
RustbotResult,
utils::{
BOT_VERSION,
GIT_COMMIT_HASH,
GIT_COMMIT_BRANCH,
format_duration
}
}; };
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") {
match var("DOCKER_HOSTNAME") {
Ok(h) => h.to_string(), Ok(h) => h.to_string(),
Err(_) => "DOCKER_HOSTNAME is empty!".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,
rustbot_lib::{
RustbotFwCtx, RustbotFwCtx,
RustbotResult 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,
CreateEmbed,
CreateEmbedAuthor,
CreateMessage,
Ready
},
rustbot_lib::{
config::BINARY_PROPERTIES,
utils::{ utils::{
BOT_VERSION, BOT_VERSION,
GIT_COMMIT_HASH, GIT_COMMIT_BRANCH,
GIT_COMMIT_BRANCH GIT_COMMIT_HASH
}, },
config::BINARY_PROPERTIES RustbotFwCtx,
}; RustbotResult
use std::sync::atomic::{ },
std::sync::atomic::{
AtomicBool, AtomicBool,
Ordering::Relaxed Ordering::Relaxed
}; }
use poise::serenity_prelude::{
Ready,
ChannelId,
CreateMessage,
CreateEmbed,
CreateEmbedAuthor
}; };
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 {
std::{
future::Future,
sync::Arc
},
tokio::{
task, task,
time::{ time::{
interval, interval,
Duration 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

@ -11,33 +11,35 @@ pub struct ConfigMeta {
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,12 +53,7 @@ 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()

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,11 +34,11 @@ 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| {
Box::pin(async move {
let get_guild_channel_name = match ctx.guild_channel().await { let get_guild_channel_name = match ctx.guild_channel().await {
Some(channel) => format!("in #{}", channel.name.clone()), Some(channel) => format!("in #{}", channel.name.clone()),
None => String::from("") None => String::from("")
@ -53,7 +55,8 @@ async fn main() {
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| {
Box::pin(async move {
match error { match error {
poise::FrameworkError::Command { error, ctx, .. } => { poise::FrameworkError::Command { error, ctx, .. } => {
println!("PoiseCommandError({}): {}", ctx.command().qualified_name, error); println!("PoiseCommandError({}): {}", ctx.command().qualified_name, error);
ctx.reply(format!( ctx
.reply(format!(
"Encountered an error during command execution, ask {} to check console for more details!", "Encountered an error during command execution, ask {} to check console for more details!",
mention_dev(ctx).unwrap_or_default() mention_dev(ctx).unwrap_or_default()
)).await.expect("Error sending message"); ))
.await
.expect("Error sending message");
}, },
poise::FrameworkError::EventHandler { error, event, .. } => println!("PoiseEventHandlerError({}): {}", event.snake_case_name(), error), poise::FrameworkError::EventHandler { error, event, .. } => println!("PoiseEventHandlerError({}): {}", event.snake_case_name(), error),
poise::FrameworkError::NotAnOwner { ctx, .. } => { poise::FrameworkError::NotAnOwner { ctx, .. } => {
println!("PoiseNotAnOwner: {} tried to execute a developer-level command ({})", ctx.author().name, ctx.command().qualified_name); println!(
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"); "PoiseNotAnOwner: {} tried to execute a developer-level command ({})",
ctx.author().name,
ctx.command().qualified_name
);
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::UnknownInteraction { interaction, .. } => println!( poise::FrameworkError::UnknownInteraction { interaction, .. } => println!(
"PoiseUnknownInteractionError: {} tried to execute an unknown interaction ({})", "PoiseUnknownInteractionError: {} tried to execute an unknown interaction ({})",
interaction.user.name, interaction.user.name, interaction.data.name
interaction.data.name
), ),
poise::FrameworkError::UnknownCommand { msg, .. } => println!( poise::FrameworkError::UnknownCommand { msg, .. } => println!(
"PoiseUnknownCommandError: {} tried to execute an unknown command ({})", "PoiseUnknownCommandError: {} tried to execute an unknown command ({})",
msg.author.name, msg.author.name, msg.content
msg.content
), ),
poise::FrameworkError::ArgumentParse { ctx, error, .. } => { poise::FrameworkError::ArgumentParse { ctx, error, .. } => {
println!("PoiseArgumentParseError: {}", error); println!("PoiseArgumentParseError: {}", error);
ctx.reply(format!("Error parsing argument(s): {error}")).await.expect("Error sending message"); ctx
.reply(format!("Error parsing argument(s): {error}"))
.await
.expect("Error sending message");
}, },
poise::FrameworkError::CommandPanic { ctx, payload, .. } => { poise::FrameworkError::CommandPanic { ctx, payload, .. } => {
if let Some(payload) = payload.clone() { if let Some(payload) = payload.clone() {
println!("PoiseCommandPanic: {payload}"); println!("PoiseCommandPanic: {payload}");
ctx.reply(format!( ctx
.reply(format!(
"The command panicked, please tell my developer about this!\n**Error:**```\n{payload}\n```" "The command panicked, please tell my developer about this!\n**Error:**```\n{payload}\n```"
)).await.expect("Error sending message"); ))
.await
.expect("Error sending message");
} else { } else {
println!("PoiseCommandPanic: No payload provided"); println!("PoiseCommandPanic: No payload provided");
let uh_oh = [ let uh_oh = [
"Well, this is concerning... Hopefully you notified my developer about this!", "Well, this is concerning... Hopefully you notified my developer about this!",
"The command panicked, but didn't leave any trace behind... Suspicious!", "The command panicked, but didn't leave any trace behind... Suspicious!"
].join("\n"); ]
.join("\n");
ctx.reply(uh_oh).await.expect("Error sending message"); ctx.reply(uh_oh).await.expect("Error sending message");
} }
}, },
other => println!("PoiseOtherError: {other}") 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!")
}