feat: early steps of storage and configuration

This commit is contained in:
2026-04-09 22:39:02 -04:00
parent 7d3a309d2b
commit 7885526944
14 changed files with 393 additions and 54 deletions

178
src/command/debug.rs Normal file
View File

@@ -0,0 +1,178 @@
use std::sync::LazyLock;
use async_compression::futures::bufread::BrotliDecoder;
use capnp::message::ReaderOptions;
use futures::AsyncReadExt;
use opendal::ErrorKind;
use snafu::{OptionExt, Snafu};
use twilight_model::{
application::{
command::{Command, CommandType},
interaction::Interaction,
},
channel::message::{Embed, MessageFlags},
http::interaction::{InteractionResponse, InteractionResponseType},
id::{Id, marker::UserMarker},
};
use twilight_util::builder::{
InteractionResponseDataBuilder,
command::CommandBuilder,
embed::{EmbedBuilder, EmbedFieldBuilder},
};
use crate::{bot_capnp, command::State};
const NAME: &str = "debug";
const DESCRIPTION: &str =
"(Only the bot owner can use this) Show various information for debugging purposes";
pub static COMMAND: LazyLock<Command> = LazyLock::new(|| {
CommandBuilder::new(NAME, DESCRIPTION, CommandType::ChatInput)
.validate()
.expect("command wasn't correct")
.build()
});
#[derive(Debug, Snafu)]
enum NoPermission {
/// there isn't a user who invoked this command
NoUser,
/// the user isn't allowed to use this command because they're not the bot owner
NotInvokedByBotOwner,
}
fn no_permission_to_embed(error: NoPermission) -> Embed {
match error {
NoPermission::NoUser => {
EmbedBuilder::new().title("Not invoked by a user").description("This command works by joining the same VC as the user, but this bot didn't receive any user data. So did no user invoke it?! (This error should be impossible!)").validate().unwrap().build()
},
NoPermission::NotInvokedByBotOwner => {
EmbedBuilder::new().title("No permission to see debug info").description("Only the owner of this bot is allowed to see its information for debugging purposes.").validate().unwrap().build()
},
}
}
fn check_permission(
interaction: &Interaction,
bot_owner_user_id: Id<UserMarker>,
) -> Result<(), NoPermission> {
let user_id = interaction
.member
.as_ref()
.and_then(|member| member.user.as_ref().map(|user| user.id))
.context(NoUserSnafu)?;
if user_id != bot_owner_user_id {
return Err(NoPermission::NotInvokedByBotOwner);
}
Ok(())
}
#[tracing::instrument]
pub async fn handle(state: State, interaction: Interaction) {
if let Err(no_permission) = check_permission(&interaction, state.discord_bot_owner_user_id) {
state
.discord_client
.interaction(state.discord_application_id)
.create_response(
interaction.id,
&interaction.token,
&InteractionResponse {
kind: InteractionResponseType::ChannelMessageWithSource,
data: Some(
InteractionResponseDataBuilder::new()
.embeds([no_permission_to_embed(no_permission)])
.flags(MessageFlags::EPHEMERAL)
.build(),
),
},
)
.await
.expect("TODO");
return;
}
state
.discord_client
.interaction(state.discord_application_id)
.create_response(
interaction.id,
&interaction.token,
&InteractionResponse {
kind: InteractionResponseType::ChannelMessageWithSource,
data: Some(
InteractionResponseDataBuilder::new()
.flags(MessageFlags::EPHEMERAL)
.content("some debug info is coming your way!")
.build(),
),
},
)
.await
.expect("TODO");
let heat_script_description = {
let compressed_result = state
.bot_data
.reader("data.bin.brotli")
.await
.expect("TODO")
.into_futures_async_read(..)
.await;
let mut buf = Vec::default();
let mut message = capnp::message::TypedBuilder::<bot_capnp::bot::Owned>::new_default();
let fallback = message.init_root();
let message_reader;
let mut bot_data = fallback.into_reader();
match compressed_result {
Ok(compressed) => {
let mut decompressed = BrotliDecoder::new(compressed);
decompressed.read_to_end(&mut buf).await.expect("TODO");
message_reader =
capnp::serialize_packed::read_message(&*buf, ReaderOptions::default())
.expect("TODO");
bot_data = message_reader
.get_root::<bot_capnp::bot::Reader>()
.expect("TODO");
}
Err(error) if error.kind() == ErrorKind::NotFound => {
tracing::error!("TODO: proceeding with fallback");
}
Err(error) => {
tracing::error!(?error, "TODO");
return;
}
}
let heat_script_option = bot_data
.has_heat_script()
.then(|| bot_data.get_heat_script().expect("TODO"));
let heat_script_option =
heat_script_option.map(|heat_script| heat_script.to_str().expect("TODO"));
heat_script_option.map_or("none set yet".into(), |heat_script| {
format!("```\n{heat_script}\n```")
})
};
state
.discord_client
.interaction(state.discord_application_id)
.create_followup(&interaction.token)
.embeds(&[EmbedBuilder::new()
.field(EmbedFieldBuilder::new("Heat Script", heat_script_description).build())
.validate()
.unwrap()
.build()])
.flags(MessageFlags::EPHEMERAL)
.await
.expect("TODO");
}