feat: Setup Config question

This commit is contained in:
Max batleforc 2025-05-27 19:22:12 +02:00
parent 7f8aebb1e5
commit d026347897
No known key found for this signature in database
GPG Key ID: 25D243AB4B6AC9E7
13 changed files with 259 additions and 8 deletions

1
Cargo.lock generated
View File

@ -713,6 +713,7 @@ dependencies = [
"chrono",
"clickhouse",
"clickhouse_pool",
"poise",
"serde",
"serde_json",
"serde_repr",

View File

@ -20,7 +20,7 @@
- [x] [ADMIN] trivial create : Creer une activité Trivial Daily
- [x] [ADMIN] trivial list : List les activité
- [ ] [ADMIN] trivial config : Modification des paramétre de base d'un event
- [x] [ADMIN] trivial config : Modification des paramétre de base d'un event
- [ ] [ADMIN] trivial add-question : Ajout de question + réponse ou X réponse
- [ ] [ADMIN] trivial del-question : Suppression de question
- [ ] [ADMIN] trivial load-question : Charge un fichier/lien de question
@ -29,6 +29,7 @@
- [ ] trivial score : Score d'une personne par défaut le sien
- [ ] trivial top : Top X par défaut 3
- [ ] [ADMIN] trivial balance : Modifie le score d'une personne +/-/=
- [ ] [ADMIN] trivial status : Return trvial status or change trivial status
#### TODO

View File

@ -70,3 +70,31 @@ pub async fn is_user_admin_right(ctx: Context<'_>) -> Result<bool, Error> {
);
Ok(false)
}
// pub async fn autocomplete_channel_that_have_trivia(
// ctx: Context<'_>,
// _partial: &str,
// ) -> impl Iterator<Item = String> {
// let guild_id = match ctx.guild_id() {
// Some(id) => id.get(),
// None => {
// ctx.say("This command can only be used in a server.").await;
// return vec![];
// }
// };
// let manager = ctx.data().datalake_config.clone();
// let search_query = Trivial::build_select_query(Some(&format!("id = {}", guild_id)), None, None);
// let trivials = manager
// .execute_select_with_retry::<Trivial>(&search_query)
// .await;
// if trivials.is_empty() {
// return vec![];
// }
// trivials
// .iter()
// .map(|c| ChannelId::from(c.channel_id).to_string())
// .collect()
// }

View File

@ -0,0 +1,149 @@
use crate::bot::helper::is_user_admin_right;
use crate::bot::{Context, Error};
use clickhouse_pool::traits::Model;
use database::trivial::{Trivial, TrivialRewardKind};
use poise::serenity_prelude::{Channel, Role};
use tracing::{debug, info, instrument};
/// Config an existing trivia game
#[instrument(name="trivia_config", skip(ctx), level = "info", fields(channel = ctx.channel_id().get(), guild = ?ctx.guild_id().unwrap().get()))]
#[poise::command(
prefix_command,
slash_command,
guild_only,
category = "Trivia",
check = "is_user_admin_right"
)]
pub async fn config(
ctx: Context<'_>,
#[description = "Channel of the trivia game, or current"] channel_id: Option<Channel>,
#[description = "Name of the trivia game"] name: Option<String>,
#[description = "Description of the trivia game"] description: Option<String>,
#[description = "Whether to use random questions"] random_question: Option<bool>,
#[description = "Role to ping when a question is asked"] role_ping: Option<Role>,
#[description = "Whether to enable role ping"] role_ping_enabled: Option<bool>,
#[description = "Reward kind for the trivia game"] reward_kind: Option<TrivialRewardKind>,
#[description = "Reward amount for the trivia game"] reward_amount: Option<u64>,
#[description = "Whether to send an ephemeral message when the answer is taken into account"]
taken_into_account: Option<bool>,
) -> Result<(), Error> {
let guild_id = match ctx.guild_id() {
Some(id) => id.get(),
None => {
ctx.say("This command can only be used in a server.")
.await?;
return Ok(());
}
};
let channel_id = match channel_id {
Some(c) => c.id().get(),
None => {
info!("No channel specified, using current channel.");
ctx.channel_id().get()
}
};
let manager = ctx.data().datalake_config.clone();
let seach_query = Trivial::build_select_query(
Some(&format!(
"channel_id = {} and guild_id = {}",
channel_id, guild_id
)),
None,
None,
);
let mut trivia_exists: Trivial = match manager
.execute_select_with_retry::<Trivial>(&seach_query)
.await
{
Ok(trivia) => {
if trivia.is_empty() {
ctx.say("No trivia game found in this channel. Please create one first.")
.await?;
return Ok(());
}
if trivia.len() > 1 {
ctx.say("Multiple trivia games found in this channel. Please contact an admin to resolve this.")
.await?;
return Ok(());
}
trivia[0].clone()
}
Err(e) => {
ctx.say(format!("Error fetching trivia game: {}", e))
.await?;
return Err(Error::from(e));
}
};
debug!("Found trivia game: {:?}", trivia_exists);
let mut inserter = match manager.get_insert::<Trivial>(Trivial::table_name()).await {
Ok(inserter) => inserter,
Err(e) => {
tracing::error!("Failed to create inserter for Trivial: {}", e);
ctx.say("Failed to update trivia game. Please try again later.")
.await?;
return Ok(());
}
};
trivia_exists.sign = -1; // Set sign to -1 to indicate an update
inserter.write(&trivia_exists.clone()).await?;
// Update the trivia game with provided options
if let Some(name) = name {
trivia_exists.name = name;
}
if let Some(description) = description {
trivia_exists.description = description;
}
if let Some(random_question) = random_question {
trivia_exists.random_question = random_question;
}
if let Some(role_ping) = role_ping {
trivia_exists.role_ping = role_ping.id.get();
}
if let Some(role_ping_enabled) = role_ping_enabled {
trivia_exists.role_ping_enabled = role_ping_enabled;
}
if let Some(reward_kind) = reward_kind {
trivia_exists.reward_kind = reward_kind;
}
if let Some(reward_amount) = reward_amount {
trivia_exists.reward_amount = reward_amount;
}
if let Some(taken_into_account) = taken_into_account {
trivia_exists.taken_into_account = taken_into_account;
}
trivia_exists.updated_at = chrono::Utc::now();
trivia_exists.updater_id = ctx.author().id.get();
debug!("Updated trivia game: {:?}", trivia_exists);
// Set sign to 1 to indicate a successful update
trivia_exists.sign = 1;
// Write the updated trivia game back to the database
inserter.write(&trivia_exists).await?;
match inserter.end().await {
Ok(_) => {
info!(
"Trivia game '{}' updated successfully in channel <#{}>.",
trivia_exists.name, channel_id
);
ctx.say(format!(
"Trivia game '{}' has been successfully updated.",
trivia_exists.name
))
.await?;
}
Err(e) => {
tracing::error!("Failed to update trivia game: {}", e);
ctx.say("Failed to update trivia game. Please try again later.")
.await?;
return Ok(());
}
}
let optimize_querry = format!("OPTIMIZE TABLE {} FINAL", Trivial::table_name());
if let Err(e) = manager.execute_with_retry(&optimize_querry).await {
tracing::error!("Failed to optimize trivia table: {}", e);
} else {
info!("Trivia table optimized successfully.");
}
Ok(())
}

View File

@ -1,7 +1,9 @@
pub mod config;
pub mod create;
pub mod list;
use crate::bot::{Context, Error};
use config::config;
use create::create;
use list::list;
use tracing::instrument;
@ -12,7 +14,7 @@ use tracing::instrument;
slash_command,
prefix_command,
category = "Trivia",
subcommands("create", "list"),
subcommands("create", "list", "config"),
guild_only = true
)]
pub async fn trivia(ctx: Context<'_>) -> Result<(), Error> {

View File

@ -61,6 +61,7 @@ impl PoolManager {
.get_connection()
.await
.unwrap()
.clone()
.insert(table_name)
}

View File

@ -2,6 +2,7 @@ pub trait Model {
type T;
fn create_table_sql() -> &'static str;
fn optimize_table_sql() -> &'static str;
fn table_name() -> &'static str;
fn column_names() -> Vec<&'static str>;
fn to_row(&self) -> (Vec<&'static str>, Vec<String>);

View File

@ -11,6 +11,7 @@ clickhouse_pool = { path = "../clickhouse_pool" }
clickhouse = { workspace = true }
uuid = { workspace = true }
chrono = { workspace = true }
poise = { workspace = true }
serde_repr = "0.1.20"
serde_json = { workspace = true }

View File

@ -30,6 +30,13 @@ impl Model for Guild {
"guild"
}
fn optimize_table_sql() -> &'static str {
r#"
OPTIMIZE TABLE guild FINAL
"#
.trim()
}
fn create_table_sql() -> &'static str {
r#"
CREATE TABLE IF NOT EXISTS guild (

View File

@ -5,20 +5,49 @@ use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize_repr, Deserialize_repr)]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize_repr,
Deserialize_repr,
poise::ChoiceParameter,
)]
#[repr(u8)]
pub enum TrivialRewardKind {
#[name = "Only the first one"]
OnlyTheFirstOne = 0,
#[name = "Top three"]
TopThree = 1,
#[name = "Top five"]
TopFive = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize_repr, Deserialize_repr)]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize_repr,
Deserialize_repr,
poise::ChoiceParameter,
)]
#[repr(u8)]
pub enum TrivialStatus {
#[name = "Init"]
Init = 0,
#[name = "Started"]
Started = 1,
#[name = "Finished"]
Finished = 2,
#[name = "Paused"]
Paused = 3,
}
@ -48,6 +77,7 @@ pub struct Trivial {
/// Whether or not the bot should send an ephemeral message to the user when their answer is taken into account.
pub taken_into_account: bool,
pub status: TrivialStatus,
pub sign: i8,
}
impl Default for Trivial {
@ -69,6 +99,7 @@ impl Default for Trivial {
reward_amount: 3,
taken_into_account: true,
status: TrivialStatus::Init,
sign: 1,
}
}
}
@ -100,11 +131,14 @@ impl Model for Trivial {
fn table_name() -> &'static str {
"trivial"
}
fn optimize_table_sql() -> &'static str {
"OPTIMIZE TABLE trivial FINAL"
}
fn create_table_sql() -> &'static str {
r#"
CREATE TABLE IF NOT EXISTS trivial (
id UUID PRIMARY KEY,
id UUID,
name String,
description String,
guild_id UInt64,
@ -119,9 +153,11 @@ impl Model for Trivial {
reward_kind Enum8('OnlyTheFirstOne' = 0, 'TopThree' = 1, 'TopFive' = 2),
reward_amount UInt64,
taken_into_account Bool,
status Enum8('Init' = 0, 'Started' = 1, 'Finished' = 2, 'Paused' = 3)
) ENGINE = MergeTree()
ORDER BY id
status Enum8('Init' = 0, 'Started' = 1, 'Finished' = 2, 'Paused' = 3),
sign Int8
) ENGINE = CollapsingMergeTree(sign)
PRIMARY KEY (guild_id, id)
ORDER BY (guild_id, id)
"#
}
@ -143,6 +179,7 @@ impl Model for Trivial {
"reward_amount",
"taken_into_account",
"status",
"sign",
]
}
@ -166,6 +203,7 @@ impl Model for Trivial {
self.reward_amount.to_string(),
self.taken_into_account.to_string(),
format!("'{:?}'", serde_json::to_string(&self.status)),
self.sign.to_string(),
],
)
}
@ -213,6 +251,7 @@ impl Model for Trivial {
if let Some(offset) = offset {
query.push_str(&format!(" OFFSET {}", offset));
}
//query.push_str(" FINAL");
query
}
}

View File

@ -30,6 +30,13 @@ impl Model for TrivialPoint {
"trivial_point"
}
fn optimize_table_sql() -> &'static str {
r#"
OPTIMIZE TABLE trivial_point FINAL
"#
.trim()
}
fn create_table_sql() -> &'static str {
r#"
CREATE TABLE IF NOT EXISTS trivial_point (

View File

@ -30,6 +30,13 @@ impl Model for TrivialQuestion {
"trivial_question"
}
fn optimize_table_sql() -> &'static str {
r#"
OPTIMIZE TABLE trivial_question FINAL
"#
.trim()
}
fn create_table_sql() -> &'static str {
r#"
CREATE TABLE IF NOT EXISTS trivial_question (

View File

@ -29,6 +29,13 @@ impl Model for TrivialRound {
"trivial_round"
}
fn optimize_table_sql() -> &'static str {
r#"
OPTIMIZE TABLE trivial_round FINAL
"#
.trim()
}
fn create_table_sql() -> &'static str {
r#"
CREATE TABLE IF NOT EXISTS trivial_round (