stat_summoner/
main.rs

1mod embed;
2mod interactions;
3mod law;
4mod models;
5mod module;
6mod riot_api;
7mod utils;
8
9use std::sync::Arc;
10
11use interactions::handle_button_click;
12use models::data::Data;
13use module::askingforflex::askingforflex::askingforflex;
14use module::championsinfos::championsinfos::championsinfos;
15use module::followgames::followgames::followgames;
16use module::lolstats::lolstats::lolstats;
17use module::loop_module::loop_module::{check_and_update_db, fetch_champion_data};
18use module::randomchampions::randomchampions::randomchampions;
19use module::suggestions::suggestions::suggestion;
20use module::whoisfollowed::whoisfollowed::whoisfollowed;
21use mongodb::bson::doc;
22use mongodb::{
23    options::{ClientOptions, ServerApi, ServerApiVersion},
24    Client,
25};
26use poise::serenity_prelude::{self as serenity, FullEvent};
27use shuttle_runtime::SecretStore;
28use shuttle_serenity::ShuttleSerenity;
29use tokio::sync::RwLock;
30use tokio::time::{sleep, Duration};
31
32/// ⚙️ **Function**: Initializes and starts the Discord bot using the Shuttle runtime and Poise framework.
33///
34/// This function is the entry point for the Discord bot. It retrieves secrets (like the Discord token and Riot API key)
35/// from the Shuttle runtime, sets up the bot's framework with its registered commands, and then starts the bot client
36/// with the required intents.
37///
38/// # Parameters:
39/// - `secret_store`: The Shuttle runtime secret store, which holds sensitive information such as the Discord token and Riot API key.
40///
41/// # Returns:
42/// - `ShuttleSerenity`: An instance of the Serenity client wrapped in a result. It starts the bot client once all setup is complete.
43///
44/// # ⚠️ Notes:
45/// - The bot framework is built using the Poise framework, which is designed for building Discord bots easily.
46/// - The `lolstats` command is registered globally, meaning it will be available in all servers the bot is in.
47/// - The function uses non-privileged gateway intents, meaning it doesn't request sensitive Discord information such as message content or member lists.
48///
49/// # Example:
50/// This function is called automatically when the bot is deployed and run in the Shuttle environment.
51///
52/// ```rust
53/// #[shuttle_runtime::main]
54/// async fn main(secret_store: SecretStore) -> ShuttleSerenity {
55///     // Bot setup and startup code
56/// }
57/// ```
58///
59/// The bot will start and listen to commands like `lolstats` once it is running.
60#[shuttle_runtime::main]
61async fn main(#[shuttle_runtime::Secrets] secret_store: SecretStore) -> ShuttleSerenity {
62    let discord_token = secret_store
63        .get("DISCORD_TOKEN")
64        .ok_or_else(|| anyhow::anyhow!("'DISCORD_TOKEN' was not found"))?;
65
66    let riot_api_key = secret_store
67        .get("RIOT_API_KEY")
68        .ok_or_else(|| anyhow::anyhow!("'RIOT_API_KEY' was not found"))?;
69
70    let mongodb_uri = secret_store
71        .get("MONGODB_URI")
72        .ok_or_else(|| anyhow::anyhow!("'MONGODB_URI' was not found"))?;
73    let suggestions_channel_id = secret_store
74        .get("SUGGESTIONS_CHANNEL_ID")
75        .ok_or_else(|| anyhow::anyhow!("'SUGGESTIONS_CHANNEL_ID' was not found"))?
76        .parse::<u64>()
77        .map_err(|e| anyhow::anyhow!(e))?;
78
79    let mut client_options = ClientOptions::parse(&mongodb_uri)
80        .await
81        .expect("Failed to parse MongoDB URI");
82
83    let server_api = ServerApi::builder().version(ServerApiVersion::V1).build();
84    client_options.server_api = Some(server_api);
85    let mongo_client =
86        Client::with_options(client_options).expect("Failed to create MongoDB client");
87
88    let mongo_client_clone = mongo_client.clone();
89    let mongo_client_clone_2 = mongo_client.clone();
90    let riot_api_key_clone = riot_api_key.clone();
91    let dd_json_value = riot_api::open_dd_json().await.unwrap();
92    let dd_json = Arc::new(RwLock::new(dd_json_value));
93    let dd_json_clone_for_loop = dd_json.clone();
94
95    let framework = poise::Framework::builder()
96        .options(poise::FrameworkOptions {
97            commands: vec![
98                lolstats(),
99                followgames(),
100                whoisfollowed(),
101                championsinfos(),
102                randomchampions(),
103                suggestion(),
104                askingforflex(),
105            ],
106            event_handler: |ctx, event, _framework, data| {
107                Box::pin(async move {
108                    if let FullEvent::InteractionCreate { interaction } = event {
109                        if let Err(e) =
110                            handle_button_click(ctx.clone(), interaction.clone(), data).await
111                        {
112                            log::error!("Erreur dans handle_button_click : {:?}", e);
113                        }
114                    }
115                    Ok(())
116                })
117            },
118
119            ..Default::default()
120        })
121        .setup(move |_ctx, _ready, _framework| {
122            let riot_api_key = riot_api_key.clone();
123            let mongo_client = mongo_client.clone();
124            let dd_json = dd_json.clone();
125            let suggestions_channel_id = suggestions_channel_id.clone();
126            Box::pin(async move {
127                poise::builtins::register_globally(_ctx, &_framework.options().commands).await?;
128                Ok(Data {
129                    riot_api_key,
130                    mongo_client,
131                    dd_json,
132                    suggestions_channel_id,
133                })
134            })
135        })
136        .build();
137    let client =
138        serenity::ClientBuilder::new(discord_token, serenity::GatewayIntents::non_privileged())
139            .framework(framework)
140            .await
141            .map_err(shuttle_runtime::CustomError::new)?;
142    let http = client.http.clone();
143    tokio::spawn(async move {
144        loop {
145            match check_and_update_db(&mongo_client_clone, &riot_api_key_clone, http.clone()).await
146            {
147                Ok(_) => (),
148                Err(e) => log::error!(
149                    "Erreur lors de la vérification de la base de données : {:?}",
150                    e
151                ),
152            }
153            sleep(Duration::from_secs(120)).await;
154        }
155    });
156    tokio::spawn(async move {
157        println!("Starting champion data fetch loop...");
158        loop {
159            match fetch_champion_data(&mongo_client_clone_2).await {
160                Ok(_) => log::info!("Champion data updated successfully."),
161                Err(e) => log::error!("Error updating champion data: {:?}", e),
162            }
163            match riot_api::open_dd_json().await {
164                Ok(new_dd_json) => {
165                    let mut dd_json_write = dd_json_clone_for_loop.write().await;
166                    *dd_json_write = new_dd_json;
167                    log::info!("DataDragon JSON updated successfully.");
168                }
169                Err(e) => {
170                    log::error!("Error updating DataDragon JSON : {:?}", e);
171                }
172            }
173            sleep(Duration::from_secs(60 * 60 * 24)).await
174        }
175    });
176
177    Ok(client.into())
178}