Skip to content

Commit 41015da

Browse files
committed
changes
1 parent 264b77f commit 41015da

File tree

3 files changed

+262
-279
lines changed

3 files changed

+262
-279
lines changed

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ mod database;
4444
mod event_handler;
4545
mod math_test;
4646
mod message_handler;
47+
mod quiz_handler;
4748
mod structs;
4849
pub mod utils;
4950
mod zalgos;

src/message_handler.rs

Lines changed: 11 additions & 279 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,15 @@
1-
use rand::{
2-
prelude::{IteratorRandom, SliceRandom},
3-
seq::IndexedRandom,
4-
Rng,
5-
};
1+
use rand::{prelude::{IteratorRandom, SliceRandom}, seq::IndexedRandom, Rng};
62
use serde_rusqlite::from_row;
73
use std::{sync::Arc, time::Instant};
84
use tokio::sync::MutexGuard;
9-
use tokio::time::Instant as TokioInstant;
105
use twilight_http::Client as HttpClient;
116
use twilight_model::{gateway::payload::incoming::MessageCreate, id::Id};
127
use vesper::twilight_exports::UserMarker;
138

149
use crate::{
15-
ai_message,
16-
color_quiz::ColorQuiz,
17-
database::User,
18-
math_test::MathTest,
19-
structs::{Command, List, PendingColorTest, PendingMathTest, State},
20-
utils::levels::xp_required_for_level,
21-
zalgos::zalgify_text,
22-
RESPONDERS,
10+
ai_message, database::User, quiz_handler, structs::{Command, List, State},
11+
utils::levels::xp_required_for_level, zalgos::zalgify_text, RESPONDERS,
2312
};
24-
use twilight_model::http::attachment::Attachment;
2513

2614
pub async fn handle_message(
2715
msg: &MessageCreate,
@@ -37,276 +25,20 @@ pub async fn handle_message(
3725
}
3826
}
3927

40-
// Check if there's a pending math test in this channel (anyone can answer)
41-
if let Some(pending_test) = locked_state.pending_math_tests.get(&msg.channel_id.get()) {
42-
let elapsed = pending_test.started_at.elapsed();
43-
44-
// Clone values we need before mutable operations
45-
let question = pending_test.question.clone();
46-
let answer = pending_test.answer;
47-
let original_user_id = pending_test.user_id;
48-
49-
// Check if 30 seconds have passed
50-
if elapsed.as_secs() > 30 {
51-
locked_state.pending_math_tests.remove(&msg.channel_id.get());
52-
53-
// Timeout the original user for 1 minute
54-
let timeout_until = twilight_model::util::Timestamp::from_secs(
55-
std::time::SystemTime::now()
56-
.duration_since(std::time::UNIX_EPOCH)
57-
.unwrap()
58-
.as_secs() as i64
59-
+ 60,
60-
)
61-
.unwrap();
62-
63-
if let Some(guild_id) = msg.guild_id {
64-
match http
65-
.update_guild_member(guild_id, Id::new(original_user_id))
66-
.communication_disabled_until(Some(timeout_until))
67-
{
68-
Ok(req) => {
69-
if let Err(e) = req.exec().await {
70-
tracing::error!("Failed to execute timeout: {:?}", e);
71-
}
72-
}
73-
Err(e) => {
74-
tracing::error!("Failed to timeout user: {:?}", e);
75-
}
76-
}
77-
}
78-
79-
return Ok(Command::text(format!(
80-
"<@{}> Time's up! The answer was `{:.1}`. You've been timed out for 1 minute.",
81-
original_user_id, answer
82-
)));
83-
}
84-
85-
// Check if answer is correct (anyone in channel can answer)
86-
let user_answer = msg.content.trim();
87-
if (MathTest { question, answer }).validate_answer(user_answer) {
88-
locked_state.pending_math_tests.remove(&msg.channel_id.get());
89-
90-
// Award 50x normal XP (normal is 5-20, so this is 250-1000)
91-
let bonus_xp = locked_state.rng.gen_range(250..1000);
92-
93-
// Update user XP
94-
let db = locked_state.db.get()?;
95-
let mut statement = db.prepare("SELECT * FROM user WHERE id = ?").unwrap();
96-
if let Ok(mut user) = statement.query_one([msg.author.id.get().to_string()], |row| {
97-
from_row::<User>(row).map_err(|_| rusqlite::Error::QueryReturnedNoRows)
98-
}) {
99-
let level = user.level;
100-
let xp_required = xp_required_for_level(level);
101-
let new_xp = user.xp + bonus_xp;
102-
user.name = msg.author.name.clone();
103-
104-
if new_xp >= xp_required {
105-
let new_level = level + 1;
106-
user.level = new_level;
107-
user.xp = new_xp - xp_required;
108-
user.update_sync(&db)?;
109-
110-
return Ok(Command::text(format!(
111-
"<@{}> Correct! Well done. You earned {} XP and leveled up to level {}!",
112-
msg.author.id.get(),
113-
bonus_xp,
114-
new_level
115-
))
116-
.reply());
117-
} else {
118-
user.xp = new_xp;
119-
user.update_sync(&db)?;
120-
}
121-
}
122-
123-
return Ok(Command::text(format!(
124-
"<@{}> Correct! Well done. You earned {} XP!",
125-
msg.author.id.get(),
126-
bonus_xp
127-
))
128-
.reply());
129-
}
130-
// Wrong answer - silently ignore (don't reply)
28+
if let Some(cmd) = quiz_handler::handle_math_quiz(msg, &mut locked_state, http).await {
29+
return Ok(cmd);
13130
}
13231

133-
// Check if there's a pending color test in this channel (anyone can answer)
134-
if let Some(pending_test) = locked_state.pending_color_tests.get(&msg.channel_id.get()) {
135-
let elapsed = pending_test.started_at.elapsed();
136-
137-
let r = pending_test.r;
138-
let g = pending_test.g;
139-
let b = pending_test.b;
140-
let original_user_id = pending_test.user_id;
141-
142-
if elapsed.as_secs() > 60 {
143-
locked_state.pending_color_tests.remove(&msg.channel_id.get());
144-
145-
let timeout_until = twilight_model::util::Timestamp::from_secs(
146-
std::time::SystemTime::now()
147-
.duration_since(std::time::UNIX_EPOCH)
148-
.unwrap()
149-
.as_secs() as i64
150-
+ 60,
151-
)
152-
.unwrap();
153-
154-
if let Some(guild_id) = msg.guild_id {
155-
match http
156-
.update_guild_member(guild_id, Id::new(original_user_id))
157-
.communication_disabled_until(Some(timeout_until))
158-
{
159-
Ok(req) => {
160-
if let Err(e) = req.exec().await {
161-
tracing::error!("Failed to execute timeout: {:?}", e);
162-
}
163-
}
164-
Err(e) => {
165-
tracing::error!("Failed to timeout user: {:?}", e);
166-
}
167-
}
168-
}
169-
170-
return Ok(Command::text(format!(
171-
"<@{}> Time's up! The color was `rgb({}, {}, {})` or `#{:02x}{:02x}{:02x}`. You've been timed out for 1 minute.",
172-
original_user_id,
173-
r,
174-
g,
175-
b,
176-
r,
177-
g,
178-
b
179-
)));
180-
}
181-
182-
let user_answer = msg.content.trim();
183-
let quiz = ColorQuiz { r, g, b };
184-
185-
if quiz.validate_answer(user_answer) {
186-
locked_state.pending_color_tests.remove(&msg.channel_id.get());
187-
188-
// Award same XP as math test (250-1000)
189-
let bonus_xp = locked_state.rng.gen_range(250..1000);
190-
191-
// Update user XP
192-
let db = locked_state.db.get()?;
193-
let mut statement = db.prepare("SELECT * FROM user WHERE id = ?").unwrap();
194-
if let Ok(mut user) = statement.query_one([msg.author.id.get().to_string()], |row| {
195-
from_row::<User>(row).map_err(|_| rusqlite::Error::QueryReturnedNoRows)
196-
}) {
197-
let level = user.level;
198-
let xp_required = xp_required_for_level(level);
199-
let new_xp = user.xp + bonus_xp;
200-
user.name = msg.author.name.clone();
201-
202-
if new_xp >= xp_required {
203-
let new_level = level + 1;
204-
user.level = new_level;
205-
user.xp = new_xp - xp_required;
206-
user.update_sync(&db)?;
207-
208-
return Ok(Command::text(format!(
209-
"<@{}> Correct! The color was `rgb({}, {}, {})` or `#{:02x}{:02x}{:02x}`. You earned {} XP and leveled up to level {}!",
210-
msg.author.id.get(),
211-
r,
212-
g,
213-
b,
214-
r,
215-
g,
216-
b,
217-
bonus_xp,
218-
new_level
219-
))
220-
.reply());
221-
} else {
222-
user.xp = new_xp;
223-
user.update_sync(&db)?;
224-
}
225-
}
226-
227-
return Ok(Command::text(format!(
228-
"<@{}> Correct! The color was `rgb({}, {}, {})` or `#{:02x}{:02x}{:02x}`. You earned {} XP!",
229-
msg.author.id.get(),
230-
r,
231-
g,
232-
b,
233-
r,
234-
g,
235-
b,
236-
bonus_xp
237-
))
238-
.reply());
239-
}
240-
// Wrong answer - silently ignore (don't reply)
32+
if let Some(cmd) = quiz_handler::handle_color_quiz(msg, &mut locked_state, http).await {
33+
return Ok(cmd);
24134
}
24235

243-
// Random 1/100 chance to trigger math test
244-
let should_trigger_math = locked_state.config.openai_api_key.is_some()
245-
&& locked_state.rng.gen_range(0..100) == 42
246-
&& !locked_state.pending_math_tests.contains_key(&msg.channel_id.get())
247-
&& !locked_state.pending_color_tests.contains_key(&msg.channel_id.get());
248-
249-
if should_trigger_math {
250-
let api_key = locked_state.config.openai_api_key.clone().unwrap();
251-
let db_clone = locked_state.db.clone();
252-
253-
// Use a new RNG for the async operation
254-
let mut new_rng = rand::thread_rng();
255-
256-
match MathTest::generate(&api_key, &db_clone, &mut new_rng).await {
257-
Ok(test) => {
258-
let pending = PendingMathTest {
259-
user_id: msg.author.id.get(),
260-
channel_id: msg.channel_id.get(),
261-
question: test.question.clone(),
262-
answer: test.answer,
263-
started_at: TokioInstant::now(),
264-
};
265-
266-
locked_state.pending_math_tests.insert(msg.channel_id.get(), pending);
267-
268-
return Ok(Command::text(format!(
269-
"**MATH TEST TIME!** Solve this in 30 seconds:\n`{}`\n(Answer to 1 decimal place)",
270-
test.question
271-
)));
272-
}
273-
Err(e) => {
274-
tracing::error!("Failed to generate math test: {:?}", e);
275-
}
276-
}
36+
if let Some(cmd) = quiz_handler::trigger_math_quiz(msg, &mut locked_state).await {
37+
return Ok(cmd);
27738
}
27839

279-
let should_trigger_color = locked_state.rng.gen_range(0..100) == 42
280-
&& !locked_state.pending_color_tests.contains_key(&msg.channel_id.get())
281-
&& !locked_state.pending_math_tests.contains_key(&msg.channel_id.get());
282-
283-
if should_trigger_color {
284-
let quiz = ColorQuiz::generate(&mut locked_state.rng);
285-
286-
match quiz.generate_image() {
287-
Ok(image_data) => {
288-
let pending = PendingColorTest {
289-
user_id: msg.author.id.get(),
290-
channel_id: msg.channel_id.get(),
291-
r: quiz.r,
292-
g: quiz.g,
293-
b: quiz.b,
294-
started_at: TokioInstant::now(),
295-
};
296-
297-
locked_state.pending_color_tests.insert(msg.channel_id.get(), pending);
298-
299-
return Ok(Command::text("**COLOR QUIZ TIME!** Guess this color in 60 seconds!\nFormat: `#RRGGBB`")
300-
.attachments(vec![Attachment::from_bytes(
301-
"color.png".to_string(),
302-
image_data,
303-
1
304-
)]));
305-
}
306-
Err(e) => {
307-
tracing::error!("Failed to generate color quiz image: {:?}", e);
308-
}
309-
}
40+
if let Some(cmd) = quiz_handler::trigger_color_quiz(msg, &mut locked_state).await {
41+
return Ok(cmd);
31042
}
31143

31244
let user = {

0 commit comments

Comments
 (0)