kv, quotes, some other stuff

This commit is contained in:
2024-11-25 13:33:33 -06:00
parent 008ff5b7cf
commit 7276f5922f
13 changed files with 352 additions and 6 deletions

View File

@ -1,7 +1,6 @@
use crate::{utils, Context, Error};
use poise::CreateReply;
use serenity::all::{CreateAllowedMentions, CreateAttachment, Message};
use serenity::builder::CreateMessage;
use serenity::model::prelude::UserId;
use serenity::prelude::Mentionable;

112
src/commands/eval.rs Normal file
View File

@ -0,0 +1,112 @@
use std::{
env, fs,
process::{Command, Output, Stdio},
time::{SystemTime, UNIX_EPOCH},
};
use crate::{Context, Error};
/// Evaluate rust code
#[poise::command(prefix_command)]
pub async fn eval(
ctx: Context<'_>,
#[description = "The code to run"] code: poise::CodeBlock,
) -> Result<(), Error> {
let authed_users = ctx.data().owners.clone();
// Check if the user is an owner
if !authed_users.contains(&ctx.author().id.into()) {
ctx.say(":x: You are not authorized to run this command")
.await?;
return Ok(());
}
ctx.say(":gear: Processing...").await?;
// Create a temporary directory for the file
let temp_dir = env::temp_dir();
let unique_id = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
let file_path = temp_dir.join(format!("eval_{}.rs", unique_id));
let executable_path = temp_dir.join(format!("eval_{}.out", unique_id));
// Write the code to a temporary file
if let Err(err) = fs::write(&file_path, code.code) {
ctx.say(format!("Error writing to temporary file: {}", err))
.await?;
return Ok(());
}
// Compile the Rust file and capture stderr
let compile_output: Result<Output, _> = Command::new("rustc")
.arg(&file_path)
.arg("-o")
.arg(&executable_path)
.stderr(Stdio::piped())
.output();
match compile_output {
Ok(output) if output.status.success() => {
ctx.say(
"<:success:1310650176037453834> Compilation successful. Running the executable...",
)
.await?;
// Run the compiled executable
let output = Command::new(&executable_path).output();
match output {
Ok(output) => {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
ctx.say(format!(
"**Output:**\n```\n{}\n```\n**Errors:**\n```\n{}\n```",
if stdout.trim().is_empty() {
"<no output>"
} else {
stdout.trim()
},
if stderr.trim().is_empty() {
"<no errors>"
} else {
stderr.trim()
}
))
.await?;
}
Err(err) => {
ctx.say(format!(
"<:error:1310650177056538655> Error running the executable: {}",
err
))
.await?;
}
}
}
Ok(output) => {
// If compilation failed, display the compiler error output
let stderr = String::from_utf8_lossy(&output.stderr);
ctx.say(format!(
"<:error:1310650177056538655> Compilation failed:\n```\n{}\n```",
stderr
))
.await?;
}
Err(err) => {
ctx.say(format!(
"<:error:1310650177056538655> Error invoking rustc: {}",
err
))
.await?;
}
}
// Clean up
let _ = fs::remove_file(&file_path);
let _ = fs::remove_file(&executable_path);
Ok(())
}

View File

@ -3,4 +3,6 @@ pub mod vouch;
pub mod cta;
pub mod dog;
pub mod profile;
pub mod action;
pub mod action;
pub mod eval;
pub mod quote;

91
src/commands/quote.rs Normal file
View File

@ -0,0 +1,91 @@
use crate::{Context, Error};
use poise::CreateReply;
use serenity::all::{CreateAllowedMentions, Message, User};
use sqlx::types::time::OffsetDateTime;
#[poise::command(context_menu_command = "Quote User")]
pub async fn quote_action(
ctx: Context<'_>,
#[description = "The target message to quote"] message: Message,
) -> Result<(), Error> {
let quote = crate::structs::quote::Quote {
quote_id: 0,
user_id: message.author.id.into(),
username: message.author.name.clone(),
quote: message.content.clone(),
added_by: ctx.author().id.into(),
added_at: OffsetDateTime::now_utc(),
};
if quote.user_id == quote.added_by {
ctx.say(":x: You can't quote yourself").await?;
return Ok(());
}
ctx.data().database_controller.quote_create(quote).await?;
ctx.say(":white_check_mark: Okay, I've immortalized that message for you :)")
.await?;
Ok(())
}
/// Get a random quote
#[poise::command(slash_command)]
pub async fn random_quote(ctx: Context<'_>) -> Result<(), Error> {
let quote = ctx.data().database_controller.quote_get_random().await?;
if let Some(quote) = quote {
ctx.send(
CreateReply::default()
.content(format!(
"{}: {}\nQuoted at: <t:{}:f> by <@{}>",
quote.username,
quote.quote,
quote.added_at.unix_timestamp(),
quote.added_by
))
.allowed_mentions(CreateAllowedMentions::new().empty_users()),
)
.await?;
} else {
ctx.say("No quotes found").await?;
}
Ok(())
}
/// Get quotes for a user, showing latest 10
#[poise::command(slash_command)]
pub async fn user_quotes(
ctx: Context<'_>,
#[description = "The user to get quotes for"] user: User,
) -> Result<(), Error> {
let quotes = ctx
.data()
.database_controller
.quote_get_by_user_id(user.id.into())
.await?;
if quotes.is_empty() {
ctx.say("No quotes found").await?;
return Ok(());
}
let mut response = String::new();
for quote in quotes {
response.push_str(&format!(
"{}: {}\nQuoted at: <t:{}:f> by <@{}>\n",
quote.username,
quote.quote,
quote.added_at.unix_timestamp(),
quote.added_by
));
}
ctx.send(
CreateReply::default()
.content(response)
.allowed_mentions(CreateAllowedMentions::new().empty_users()),
)
.await?;
Ok(())
}

View File

@ -1,3 +1,4 @@
use crate::structs::quote::Quote;
use crate::structs::user::User;
use sqlx::MySqlPool;
@ -99,4 +100,80 @@ impl DatabaseController {
Ok(())
}
pub async fn kv_set(&self, key: &str, value: &str) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO kv_store (`key`, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value = ?",
key,
value,
value
)
.execute(&self.db)
.await?;
Ok(())
}
pub async fn kv_get(&self, key: &str) -> Result<Option<String>, sqlx::Error> {
let kv = sqlx::query!("SELECT * FROM kv_store WHERE `key` = ?", key)
.fetch_optional(&self.db)
.await?;
match kv {
Some(kv) => Ok(kv.value),
None => Ok(None),
}
}
pub async fn quote_create(&self, quote: Quote) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO quotes (user_id, username, quote, added_by) VALUES (?, ?, ?, ?)",
quote.user_id,
quote.username,
quote.quote,
quote.added_by
)
.execute(&self.db)
.await?;
Ok(())
}
pub async fn quote_get_random(&self) -> Result<Option<Quote>, sqlx::Error> {
let quote = sqlx::query!("SELECT * FROM quotes ORDER BY RAND() LIMIT 1")
.fetch_optional(&self.db)
.await?;
match quote {
Some(quote) => Ok(Some(Quote {
quote_id: quote.quote_id,
user_id: quote.user_id,
username: quote.username,
quote: quote.quote,
added_by: quote.added_by,
added_at: quote.added_at.unwrap(),
})),
None => Ok(None),
}
}
pub async fn quote_get_by_user_id(&self, user_id: u64) -> Result<Vec<Quote>, sqlx::Error> {
let quote = sqlx::query!("SELECT * FROM quotes WHERE user_id = ?", user_id)
.fetch_all(&self.db)
.await?;
let mut quotes = Vec::new();
for q in quote {
quotes.push(Quote {
quote_id: q.quote_id,
user_id: q.user_id,
username: q.username,
quote: q.quote,
added_by: q.added_by,
added_at: q.added_at.unwrap(),
});
}
Ok(quotes)
}
}

View File

@ -1,2 +1,2 @@
pub mod db;
pub mod join;
pub mod db;

View File

@ -4,6 +4,8 @@ mod handlers;
mod structs;
mod utils;
use std::sync::Arc;
use events::event_handler;
use handlers::db::DatabaseController;
use serde::{Deserialize, Serialize};
@ -78,6 +80,7 @@ async fn init_sqlx() -> MySqlPool {
struct Data {
database_controller: DatabaseController,
owners: Vec<u64>,
uptime: std::time::Instant,
config: Config,
vouch_store: Mutex<Vec<Vouch>>,
@ -99,6 +102,7 @@ struct Channels {
main: u64,
logs_public: u64,
logs_mod: u64,
starboard: u64,
}
#[derive(Deserialize, Serialize)]
@ -136,6 +140,7 @@ async fn main() {
main: 0,
logs_public: 0,
logs_mod: 0,
starboard: 0,
},
roles: Roles {
admin: 0,
@ -160,7 +165,9 @@ async fn main() {
let pool = init_sqlx().await;
info!("initializing bot");
let intents = GatewayIntents::privileged();
let intents = GatewayIntents::privileged()
| GatewayIntents::GUILD_MEMBERS
| GatewayIntents::GUILD_MESSAGES;
let framework = poise::Framework::builder()
.options(poise::FrameworkOptions::<Data, Error> {
@ -173,10 +180,22 @@ async fn main() {
commands::action::use_action_hug(),
commands::action::use_action_kiss(),
commands::action::use_action_pat(),
commands::eval::eval(),
commands::quote::quote_action(),
commands::quote::random_quote(),
commands::quote::user_quotes(),
],
event_handler: |ctx, event, framework, data| {
Box::pin(event_handler(ctx, event, framework, data))
},
prefix_options: poise::PrefixFrameworkOptions {
prefix: Some("~".into()),
edit_tracker: Some(Arc::new(poise::EditTracker::for_timespan(
std::time::Duration::from_secs(3600),
))),
case_insensitive_commands: true,
..Default::default()
},
..Default::default()
})
.setup(|ctx, _ready, framework| {
@ -188,6 +207,8 @@ async fn main() {
uptime: std::time::Instant::now(),
config,
vouch_store: Mutex::new(Vec::new()),
// Sticks, Emi, Katie
owners: vec![1017196087276220447, 272871217256726531, 1033331958291369984],
})
})
})

View File

@ -1,2 +1,3 @@
pub mod quote;
pub mod user;
pub mod vouch;
pub mod user;

11
src/structs/quote.rs Normal file
View File

@ -0,0 +1,11 @@
use sqlx::types::time::OffsetDateTime;
#[derive(Debug)]
pub struct Quote {
pub quote_id: i32,
pub user_id: i64,
pub username: String,
pub quote: String,
pub added_by: i64,
pub added_at: OffsetDateTime,
}