Update template with changes

This commit is contained in:
toast 2024-11-27 21:50:28 +11:00
parent 1afad09caf
commit 56e1419b32
19 changed files with 786 additions and 423 deletions

881
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@ cargo_toml = "0.20.5"
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", "rt-multi-thread"] } tokio = { version = "1.40.0", features = ["macros", "signal", "rt-multi-thread"] }
reqwest = { version = "0.12.8", features = ["native-tls-vendored"] } reqwest = { version = "0.12.8", features = ["native-tls-vendored"] }
[dependencies] [dependencies]

View File

@ -1,25 +1,22 @@
mod ready; mod ready;
mod shards; mod shards;
use poise::serenity_prelude::FullEvent;
use rustbot_lib::{ use rustbot_lib::{
RustbotError, RustbotFwCtx,
RustbotData RustbotResult
};
use poise::{
FrameworkContext,
serenity_prelude::FullEvent
}; };
pub const RUSTBOT_EVENT: &str = "RustbotEvent"; pub const RUSTBOT_EVENT: &str = "RustbotEvent";
struct EventProcessor<'a> { struct EventProcessor<'a> {
framework: FrameworkContext<'a, RustbotData, RustbotError> framework: RustbotFwCtx<'a>
} }
pub async fn processor( pub async fn processor(
framework: FrameworkContext<'_, RustbotData, RustbotError>, framework: RustbotFwCtx<'_>,
event: &FullEvent event: &FullEvent
) -> Result<(), RustbotError> { ) -> RustbotResult<()> {
let processor = EventProcessor { framework }; let processor = EventProcessor { framework };
match event { match event {

View File

@ -1,11 +1,11 @@
use crate::PoiseFwCtx;
use super::{ use super::{
EventProcessor, EventProcessor,
RUSTBOT_EVENT RUSTBOT_EVENT
}; };
use rustbot_lib::{ use rustbot_lib::{
RustbotError, RustbotFwCtx,
RustbotResult,
utils::{ utils::{
BOT_VERSION, BOT_VERSION,
GIT_COMMIT_HASH, GIT_COMMIT_HASH,
@ -15,7 +15,7 @@ use rustbot_lib::{
}; };
use std::sync::atomic::{ use std::sync::atomic::{
AtomicBool, AtomicBool,
Ordering Ordering::Relaxed
}; };
use poise::serenity_prelude::{ use poise::serenity_prelude::{
Ready, Ready,
@ -29,8 +29,8 @@ static READY_ONCE: AtomicBool = AtomicBool::new(false);
async fn ready_once( async fn ready_once(
ready: &Ready, ready: &Ready,
framework: PoiseFwCtx<'_> framework: RustbotFwCtx<'_>
) -> Result<(), RustbotError> { ) -> 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);
@ -57,8 +57,8 @@ impl EventProcessor<'_> {
pub async fn on_ready( pub async fn on_ready(
&self, &self,
data_about_bot: &Ready data_about_bot: &Ready
) -> Result<(), RustbotError> { ) -> RustbotResult<()> {
if !READY_ONCE.swap(true, Ordering::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");
} }

View File

@ -4,14 +4,14 @@ use super::{
}; };
use std::num::NonZero; use std::num::NonZero;
use rustbot_lib::RustbotError; use rustbot_lib::RustbotResult;
use poise::serenity_prelude::ShardStageUpdateEvent; use poise::serenity_prelude::ShardStageUpdateEvent;
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>
) -> Result<(), RustbotError> { ) -> 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!");
@ -21,7 +21,7 @@ impl EventProcessor<'_> {
pub async fn on_shards_stageupdate( pub async fn on_shards_stageupdate(
&self, &self,
event: &ShardStageUpdateEvent event: &ShardStageUpdateEvent
) -> Result<(), RustbotError> { ) -> RustbotResult<()> {
println!("{RUSTBOT_EVENT}[ShardStageUpdate:S{}]: {event:#?}", event.shard_id); println!("{RUSTBOT_EVENT}[ShardStageUpdate:S{}]: {event:#?}", event.shard_id);
Ok(()) Ok(())

View File

@ -1,19 +1,10 @@
pub mod events; pub mod events;
// use serde_json::json; // use serde_json::json;
use rustbot_lib::{ /* use poise::serenity_prelude::{
RustbotData,
RustbotError
};
use poise::{
FrameworkContext,
/* serenity_prelude::{
Context, Context,
WebhookId WebhookId
} */ }; */
};
type PoiseFwCtx<'a> = FrameworkContext<'a, RustbotData, RustbotError>;
/* async fn hook_logger( /* async fn hook_logger(
ctx: &Context, ctx: &Context,

View File

@ -1,7 +1,7 @@
use std::sync::LazyLock; use std::sync::LazyLock;
pub struct ConfigMeta { pub struct ConfigMeta {
pub env: String, 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>
@ -13,16 +13,16 @@ pub static BINARY_PROPERTIES: LazyLock<ConfigMeta> = LazyLock::new(ConfigMeta::n
#[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() ConfigMeta::new()
.env(String::from("dev")) .env("dev")
.embed_color(0xf1d63c) .embed_color(0xf1d63c)
); );
impl ConfigMeta { impl ConfigMeta {
fn new() -> Self { fn new() -> Self {
Self { Self {
env: String::from("prod"), env: "prod",
embed_color: 0xf1d63c, embed_color: 0xf1d63c,
rustbot_logs: 1276874302314254448, rustbot_logs: 1311282815601741844,
developers: vec![ developers: vec![
190407856527376384 // toast.ts 190407856527376384 // toast.ts
] ]
@ -31,7 +31,7 @@ impl ConfigMeta {
// Scalable functions below; // Scalable functions below;
#[cfg(not(feature = "production"))] #[cfg(not(feature = "production"))]
fn env(mut self, env: String) -> Self { fn env(mut self, env: &'static str) -> Self {
self.env = env; self.env = env;
self self
} }

View File

@ -3,5 +3,7 @@ mod data;
pub use data::RustbotData; pub use data::RustbotData;
pub mod utils; pub mod utils;
pub type RustbotError = Box<dyn std::error::Error + Send + Sync>; type RustbotError = Box<dyn std::error::Error + Send + Sync>;
pub type RustbotCtx<'a> = poise::Context<'a, RustbotData, RustbotError>; pub type RustbotContext<'a> = poise::Context<'a, RustbotData, RustbotError>;
pub type RustbotFwCtx<'a> = poise::FrameworkContext<'a, RustbotData, RustbotError>;
pub type RustbotResult<T> = Result<T, RustbotError>;

View File

@ -21,7 +21,7 @@ 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::RustbotCtx<'_>) -> 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();
let app_owners = ctx.framework().options().owners.clone(); let app_owners = ctx.framework().options().owners.clone();
@ -40,7 +40,7 @@ pub fn mention_dev(ctx: super::RustbotCtx<'_>) -> Option<String> {
} }
} }
pub fn get_guild_name(ctx: super::RustbotCtx<'_>) -> String { pub fn get_guild_name(ctx: super::RustbotContext<'_>) -> String {
match ctx.guild() { match ctx.guild() {
Some(guild) => guild.name.clone().to_string(), Some(guild) => guild.name.clone().to_string(),
None => String::from("DM") None => String::from("DM")
@ -63,7 +63,7 @@ pub fn format_duration(secs: u64) -> String {
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(", ")

View File

@ -8,8 +8,6 @@ pub use eightball::eightball;
pub use ping::ping; pub use ping::ping;
pub use uptime::uptime; pub use uptime::uptime;
type PoiseContext<'a> = rustbot_lib::RustbotCtx<'a>;
macro_rules! collect { macro_rules! collect {
() => { () => {
vec![ vec![

View File

@ -1,53 +1,82 @@
use crate::RustbotError; use rustbot_lib::{
use super::PoiseContext; RustbotContext,
RustbotResult
};
use poise::{ use poise::{
CreateReply, CreateReply,
serenity_prelude::ChannelId serenity_prelude::{
ChannelId,
ShardId,
ShardRunnerInfo
}
}; };
async fn format_shard_info(
id: &ShardId,
runner: &ShardRunnerInfo,
ctx: &RustbotContext<'_>
) -> String {
let mut string = String::new();
let heartbeat = match runner.latency {
Some(lat) => format!("`{}ms`", lat.as_millis()),
None => "Waiting for heartbeat...".to_string()
};
let status = runner.stage.to_string();
let shard_count = ctx.cache().shard_count();
let guild_count = ctx.cache().guilds().into_iter().filter(|g| g.shard_id(shard_count) == id.0).count() as u64;
string.push_str(&format!("**Shard {id}**\n"));
string.push_str(&format!("> Heartbeat: {heartbeat}\n"));
string.push_str(&format!("> Status: `{status}`\n"));
string.push_str(&format!("> Guilds: **{guild_count}**"));
string
}
/// Developer commands /// Developer commands
#[poise::command( #[poise::command(
slash_command,
prefix_command, prefix_command,
slash_command,
owners_only, owners_only,
install_context = "Guild|User",
interaction_context = "Guild|BotDm|PrivateChannel",
subcommands("deploy", "servers", "shards", "echo") subcommands("deploy", "servers", "shards", "echo")
)] )]
pub async fn dev(_: PoiseContext<'_>) -> Result<(), RustbotError> { 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)]
async fn deploy(ctx: PoiseContext<'_>) -> Result<(), RustbotError> { async fn deploy(ctx: RustbotContext<'_>) -> RustbotResult<()> {
poise::builtins::register_application_commands_buttons(ctx).await?; poise::builtins::register_application_commands_buttons(ctx).await?;
Ok(()) Ok(())
} }
/// View how many servers the bot is in /// View how many servers the bot is in
#[poise::command(prefix_command)] #[poise::command(slash_command)]
async fn servers(ctx: PoiseContext<'_>) -> Result<(), RustbotError> { async fn servers(ctx: RustbotContext<'_>) -> RustbotResult<()> {
poise::builtins::servers(ctx).await?; poise::builtins::servers(ctx).await?;
Ok(()) Ok(())
} }
/// View the status of available shards /// View the status of available shards
#[poise::command(prefix_command)] #[poise::command(slash_command)]
async fn shards(ctx: PoiseContext<'_>) -> Result<(), RustbotError> { async fn shards(ctx: RustbotContext<'_>) -> RustbotResult<()> {
let shard_runners = ctx.framework().shard_manager().runners.clone(); let shard_runners = ctx.framework().shard_manager().runners.clone();
let runners = shard_runners.lock().await; let runners = shard_runners.lock().await;
if runners.is_empty() {
ctx.reply("`ShardsReady` event hasn't fired yet!").await?;
return Ok(())
}
let mut shard_info = Vec::new(); let mut shard_info = Vec::new();
for (id, runner) in runners.iter() { for (id, runner) in runners.iter() {
shard_info.push(format!( let info = format_shard_info(id, runner, &ctx).await;
"**Shard {}**\n> Heartbeat: {}\n> Status: `{}`", shard_info.push(info);
id,
match runner.latency {
Some(lat) => format!("`{}ms`", lat.as_millis()),
None => "Waiting for heartbeat...".to_string()
},
runner.stage
))
} }
ctx.reply(shard_info.join("\n\n")).await?; ctx.reply(shard_info.join("\n\n")).await?;
@ -58,11 +87,11 @@ async fn shards(ctx: PoiseContext<'_>) -> Result<(), RustbotError> {
/// Turn your message into a bot message /// Turn your message into a bot message
#[poise::command(slash_command)] #[poise::command(slash_command)]
async fn echo( async fn echo(
ctx: super::PoiseContext<'_>, 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>
) -> Result<(), RustbotError> { ) -> RustbotResult<()> {
ctx.defer_ephemeral().await?; ctx.defer_ephemeral().await?;
let channel = match channel { let channel = match channel {

View File

@ -1,22 +1,32 @@
use crate::RustbotError; use rustbot_lib::{
use super::PoiseContext; RustbotContext,
RustbotResult,
use rustbot_lib::config::BINARY_PROPERTIES; config::BINARY_PROPERTIES
};
use poise::{ use poise::{
serenity_prelude::UserId, serenity_prelude::UserId,
builtins::paginate builtins::paginate
}; };
#[derive(poise::ChoiceParameter)]
enum ResponseMode {
Normal,
Chicken
}
/// Ask the Magic 8-Ball a yes/no question and get an unpredictable answer /// Ask the Magic 8-Ball a yes/no question and get an unpredictable answer
#[poise::command( #[poise::command(
slash_command, slash_command,
install_context = "Guild|User",
interaction_context = "Guild|BotDm|PrivateChannel",
rename = "8ball" rename = "8ball"
)] )]
pub async fn eightball( pub async fn eightball(
ctx: PoiseContext<'_>, ctx: RustbotContext<'_>,
#[description = "Your yes/no question"] question: String #[description = "Your yes/no question"] question: String,
) -> Result<(), RustbotError> { #[description = "Response modes"] mode: Option<ResponseMode>
if question.to_ascii_lowercase().contains("rustbot, show list") { ) -> RustbotResult<()> {
if question.to_ascii_lowercase().contains("niko, show list") {
if ctx.author().id == UserId::new(BINARY_PROPERTIES.developers[0]) { 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 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(); let pages: Vec<&str> = chunks.iter().map(|s| s.as_str()).collect();
@ -29,7 +39,7 @@ pub async fn eightball(
} }
} }
if question.to_ascii_lowercase().contains("rustbot, show chicken list") { if question.to_ascii_lowercase().contains("niko, show chicken list") {
if ctx.author().id == UserId::new(BINARY_PROPERTIES.developers[0]) { 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 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(); let pages: Vec<&str> = chunks.iter().map(|s| s.as_str()).collect();
@ -42,10 +52,9 @@ pub async fn eightball(
} }
} }
let rand_resp = if question.to_ascii_lowercase().contains("chicken") { let rand_resp = match mode {
get_random_chicken_response() Some(ResponseMode::Chicken) => get_random_chicken_response(),
} else { _ => get_random_response()
get_random_response()
}; };
ctx.reply(format!("> {question}\n{rand_resp}")).await?; ctx.reply(format!("> {question}\n{rand_resp}")).await?;
@ -53,7 +62,7 @@ pub async fn eightball(
Ok(()) Ok(())
} }
const RESPONSES: [&str; 30] = [ const RESPONSES: [&str; 45] = [
"Reply hazy. Look it up on Google.", // no "Reply hazy. Look it up on Google.", // no
"Meh — Figure it out yourself.", // no "Meh — Figure it out yourself.", // no
"I don't know, what do you think?", // no "I don't know, what do you think?", // no
@ -88,9 +97,24 @@ const RESPONSES: [&str; 30] = [
"Sure, but with extreme cautions.", // yes "Sure, but with extreme cautions.", // yes
"What kind of stupid question is that?? No! I'm not answering that!", // no "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 "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; 35] = [ const CHICKEN_RESPONSES: [&str; 54] = [
"Cluck cluck... Reply hazy, try pecking Google.", // no "Cluck cluck... Reply hazy, try pecking Google.", // no
"Meh... Figure it out yourself, or scratch around a bit.", // no "Meh... Figure it out yourself, or scratch around a bit.", // no
"I don't know... what do you think? *pecks at ground*", // no "I don't know... what do you think? *pecks at ground*", // no
@ -126,6 +150,25 @@ const CHICKEN_RESPONSES: [&str; 35] = [
"Yes! *lays egg of approval*", // yes "Yes! *lays egg of approval*", // yes
"It's a no, better go scratch somewhere else.", // no "It's a no, better go scratch somewhere else.", // no
"Cluck-tastic! That's a definite yes.", // yes "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 { fn get_random_response() -> &'static str {

View File

@ -1,7 +1,8 @@
use crate::RustbotError;
use super::PoiseContext;
use serde::Deserialize; use serde::Deserialize;
use rustbot_lib::{
RustbotContext,
RustbotResult
};
#[derive(Deserialize)] #[derive(Deserialize)]
struct StatusPage { struct StatusPage {
@ -18,9 +19,13 @@ struct Summary {
mean: f64 mean: f64
} }
/// Check latency of the bot's WS connection and Discord's API /// Check latency between bot and WebSocket as well as Discord's API latency
#[poise::command(slash_command)] #[poise::command(
pub async fn ping(ctx: PoiseContext<'_>) -> Result<(), RustbotError> { slash_command,
install_context = "Guild|User",
interaction_context = "Guild|BotDm|PrivateChannel"
)]
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()
@ -29,6 +34,7 @@ pub async fn ping(ctx: PoiseContext<'_>) -> Result<(), RustbotError> {
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));
latencies.push(format!("WebSocket: `{:.0?}`", ctx.ping().await)); latencies.push(format!("WebSocket: `{:.0?}`", ctx.ping().await));
latencies.push(format!("Shard ID: `{}`", ctx.serenity_context().shard_id));
ctx.reply(latencies.join("\n")).await?; ctx.reply(latencies.join("\n")).await?;

View File

@ -1,6 +1,3 @@
use crate::RustbotError;
use super::PoiseContext;
use sysinfo::System; use sysinfo::System;
use uptime_lib::get; use uptime_lib::get;
use std::{ use std::{
@ -17,11 +14,15 @@ use std::{
BufReader BufReader
} }
}; };
use rustbot_lib::utils::{ use rustbot_lib::{
RustbotContext,
RustbotResult,
utils::{
BOT_VERSION, BOT_VERSION,
GIT_COMMIT_HASH, GIT_COMMIT_HASH,
GIT_COMMIT_BRANCH, GIT_COMMIT_BRANCH,
format_duration format_duration
}
}; };
fn get_os_info() -> String { fn get_os_info() -> String {
@ -63,7 +64,7 @@ fn fmt_mem(bytes: u64) -> String {
/// Retrieve host and bot uptimes /// Retrieve host and bot uptimes
#[poise::command(slash_command)] #[poise::command(slash_command)]
pub async fn uptime(ctx: PoiseContext<'_>) -> Result<(), RustbotError> { pub async fn uptime(ctx: RustbotContext<'_>) -> RustbotResult<()> {
let bot = ctx.http().get_current_user().await.unwrap(); let bot = ctx.http().get_current_user().await.unwrap();
let mut sys = System::new_all(); let mut sys = System::new_all();
sys.refresh_all(); sys.refresh_all();

View File

@ -1,8 +1,9 @@
mod commands; mod commands;
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::token_path; use rustbot_tokens::discord_token;
use poise::serenity_prelude::{ use poise::serenity_prelude::{
builder::CreateAllowedMentions, builder::CreateAllowedMentions,
ClientBuilder, ClientBuilder,
@ -14,7 +15,6 @@ use rustbot_lib::{
mention_dev, mention_dev,
get_guild_name get_guild_name
}, },
RustbotError,
RustbotData, RustbotData,
config::BINARY_PROPERTIES config::BINARY_PROPERTIES
}; };
@ -47,20 +47,19 @@ async fn main() {
}; };
println!( println!(
"Discord[{}:S{}]: {} ran {}{} {}", "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,
prefix,
ctx.command().qualified_name, ctx.command().qualified_name,
get_guild_channel_name); );
}), }),
prefix_options: poise::PrefixFrameworkOptions { prefix_options: poise::PrefixFrameworkOptions {
prefix, prefix,
case_insensitive_commands: true,
ignore_bots: true, ignore_bots: true,
execute_self_messages: false,
mention_as_prefix: false, mention_as_prefix: false,
case_insensitive_commands: true,
execute_self_messages: false,
..Default::default() ..Default::default()
}, },
on_error: |error| Box::pin(async move { on_error: |error| Box::pin(async move {
@ -118,7 +117,7 @@ async fn main() {
.build(); .build();
let mut client = ClientBuilder::new( let mut client = ClientBuilder::new(
&token_path().await.main, discord_token().await,
GatewayIntents::GUILDS GatewayIntents::GUILDS
| GatewayIntents::GUILD_MESSAGES | GatewayIntents::GUILD_MESSAGES
| GatewayIntents::MESSAGE_CONTENT | GatewayIntents::MESSAGE_CONTENT
@ -128,7 +127,14 @@ async fn main() {
.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();
tokio::spawn(async move {
shutdown::gracefully_shutdown().await;
shard_manager.shutdown_all().await;
});
if let Err(why) = client.start_autosharded().await { if let Err(why) = client.start_autosharded().await {
println!("Error starting client: {:#?}", why); println!("Error starting client: {why:#?}");
} }
} }

23
src/shutdown.rs Normal file
View File

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

View File

@ -6,6 +6,6 @@ Only things that are needing to be changed before deploying this template to rea
- library (rustbot_lib), and its references - library (rustbot_lib), and its references
- tsclient (rustbot_tokens), and its references - tsclient (rustbot_tokens), and its references
Search by Rustbot in its usual form (all-caps, pascalcase, and such) to find all the references that need to be changed too. Search by Rustbot in its usual form (ALL-CAPS, PascalCase, and such) to find all the references that need to be changed too.
(and delete this file too!) (and delete this file too!)

View File

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

View File

@ -1,5 +1,9 @@
use std::sync::LazyLock; use poise::serenity_prelude::Token;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use std::{
str::FromStr,
sync::LazyLock
};
use tokenservice_client::{ use tokenservice_client::{
TokenService, TokenService,
TokenServiceApi TokenServiceApi
@ -33,3 +37,8 @@ static TSCLIENT: LazyLock<Mutex<TSClient>> = LazyLock::new(|| Mutex::new(TSClien
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 {
Token::from_str(&token_path().await.main)
.expect("Serenity couldn't parse the bot token!")
}