kv, quotes, some other stuff
This commit is contained in:
parent
008ff5b7cf
commit
7276f5922f
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -1263,6 +1263,16 @@ version = "0.2.164"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.11"
|
||||
@ -2284,6 +2294,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"dotenv",
|
||||
"libloading",
|
||||
"poise",
|
||||
"reqwest 0.12.9",
|
||||
"serde",
|
||||
@ -2422,6 +2433,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"sqlformat",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
@ -2506,6 +2518,7 @@ dependencies = [
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing",
|
||||
"whoami",
|
||||
]
|
||||
@ -2544,6 +2557,7 @@ dependencies = [
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing",
|
||||
"whoami",
|
||||
]
|
||||
@ -2567,6 +2581,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_urlencoded",
|
||||
"sqlx-core",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
@ -7,7 +7,7 @@ edition = "2021"
|
||||
dotenv = "0.15.0"
|
||||
poise = "0.6.1"
|
||||
serenity = "0.12.4"
|
||||
sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-ring", "mysql" ] }
|
||||
sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-ring", "mysql", "time" ] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
@ -17,3 +17,4 @@ chrono = "0.4.38"
|
||||
chrono-tz = "0.10.0"
|
||||
reqwest = "0.12.9"
|
||||
serde_json = "1.0.133"
|
||||
libloading = "0.8.5"
|
||||
|
5
migrations/20241125173833_add_kv_table.sql
Normal file
5
migrations/20241125173833_add_kv_table.sql
Normal file
@ -0,0 +1,5 @@
|
||||
-- Add migration script here
|
||||
CREATE TABLE kv_store (
|
||||
`key` VARCHAR(255) NOT NULL PRIMARY KEY,
|
||||
`value` TEXT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
11
migrations/20241125180340_add_quotes_table.sql
Normal file
11
migrations/20241125180340_add_quotes_table.sql
Normal file
@ -0,0 +1,11 @@
|
||||
-- Add migration script here
|
||||
CREATE TABLE quotes (
|
||||
quote_id INT AUTO_INCREMENT PRIMARY KEY, -- Unique ID for each quote
|
||||
user_id BIGINT NOT NULL, -- Discord user ID (64-bit)
|
||||
username VARCHAR(255) NOT NULL, -- Username of the person who said the quote
|
||||
quote TEXT NOT NULL, -- The quote itself
|
||||
added_by BIGINT NOT NULL, -- Discord user ID of the person who added the quote
|
||||
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the quote was added
|
||||
INDEX (user_id), -- Index for efficient lookup by user_id
|
||||
INDEX (added_by) -- Index for efficient lookup by added_by
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
@ -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
112
src/commands/eval.rs
Normal 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(())
|
||||
}
|
@ -4,3 +4,5 @@ pub mod cta;
|
||||
pub mod dog;
|
||||
pub mod profile;
|
||||
pub mod action;
|
||||
pub mod eval;
|
||||
pub mod quote;
|
91
src/commands/quote.rs
Normal file
91
src/commands/quote.rs
Normal 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(())
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
pub mod join;
|
||||
pub mod db;
|
||||
pub mod join;
|
||||
|
23
src/main.rs
23
src/main.rs
@ -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],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,2 +1,3 @@
|
||||
pub mod vouch;
|
||||
pub mod quote;
|
||||
pub mod user;
|
||||
pub mod vouch;
|
||||
|
11
src/structs/quote.rs
Normal file
11
src/structs/quote.rs
Normal 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,
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user