stat_summoner/
embed.rs

1use crate::models::error::Error;
2use crate::models::modal::LolStatsModal;
3use crate::{
4    models::data::{Data, EmojiId},
5    utils::get_emoji,
6};
7use mongodb::Collection;
8use poise::ReplyHandle;
9use poise::{
10    serenity_prelude::{self as serenity},
11    CreateReply,
12};
13use serde_json::Value;
14use serenity::builder::{CreateEmbed, CreateEmbedFooter};
15use tokio::time::{sleep, Duration};
16
17/// ⚙️ **Function**: Creates a rich embed message displaying League of Legends player stats and match details.
18///
19/// This function constructs a `CreateEmbed` message containing information about the player's Solo/Duo and Flex ranks,
20/// top champions, and detailed information about recent matches. The generated embed is used for displaying formatted
21/// stats in Discord messages.
22///
23/// # Parameters:
24/// - `modal_data`: Contains the player's in-game name and tag, used to personalize the embed title.
25/// - `solo_rank`: A JSON-like value containing the player's Solo/Duo rank information, including tier, division, LP, wins, losses, and winrate.
26/// - `flex_rank`: A JSON-like value containing the player's Flex rank information, similar to `solo_rank`.
27/// - `champions_info`: A formatted string representing the player's top champions, their levels, and mastery points.
28/// - `match_details`: A vector of JSON-like values representing detailed match information, including K/D/A, farm, game duration, and result.
29///
30/// # Returns:
31/// - `CreateEmbed`: The formatted embed message ready to be sent in a Discord channel.
32///
33/// # ⚠️ Notes:
34/// - If no match details are available, the embed will indicate that no recent normal or ranked matches were found.
35/// - The embed displays rank information differently depending on whether the player has earned League Points (LP) in their rank.
36///
37/// # Example:
38/// ```rust
39/// let embed = create_embed(modal_data, solo_rank, flex_rank, champions_info, match_details);
40/// ctx.send(|m| m.set_embed(embed)).await?;
41/// ```
42///
43/// The resulting embed will contain information such as:
44/// ```text
45/// 📊 Stats for Faker#1234
46/// 🔱 **Solo/Duo Rank**: Gold I (100 LP)
47/// 🌀 **Flex Rank**: Silver IV (50 LP)
48/// 💥 **Top Champions**:
49/// Yasuo - Level: 7 - Points: 123456
50/// 📜 **Match Details**:
51/// Victory - **Yasuo**, 2 hours ago (Ranked Solo/Duo):
52/// K/D/A: **10/2/8** | **200 CS** | Duration: **30:45**
53/// ⏳ Played: **2 hours ago**
54/// ```
55pub async fn create_embed(
56    modal_data: &LolStatsModal,
57    solo_rank: Value,
58    flex_rank: Value,
59    champions_info: String,
60    match_details: Vec<Value>,
61    collection_emoji: Collection<EmojiId>,
62) -> Result<CreateEmbed, Error> {
63    // Récupérer les émojis pour le rang solo et flex
64    let solo_rank_tier = solo_rank["tier"].as_str().unwrap_or("Unknown");
65    let solo_emoji = get_emoji(collection_emoji.clone(), "rank", solo_rank_tier)
66        .await
67        .unwrap_or(solo_rank_tier.to_string());
68
69    let flex_rank_tier = flex_rank["tier"].as_str().unwrap_or("Unknown");
70    let flex_emoji = get_emoji(collection_emoji.clone(), "rank", flex_rank_tier)
71        .await
72        .unwrap_or(flex_rank_tier.to_string());
73
74    // Construction de la chaîne du rang Solo/Duo
75    let solo_rank_str = if solo_rank["lp"].as_i64().unwrap_or(0) > 0 {
76        if !solo_rank["division"].as_str().unwrap_or("").is_empty() {
77            format!(
78                "**{} {}** - {} LP",
79                solo_emoji,
80                solo_rank["division"].as_str().unwrap(),
81                solo_rank["lp"].as_i64().unwrap()
82            )
83        } else {
84            format!(
85                "**{}** - {} LP",
86                solo_emoji,
87                solo_rank["lp"].as_i64().unwrap()
88            )
89        }
90    } else {
91        format!("**{}**", solo_emoji)
92    };
93
94    // Construction de la chaîne du rang Flex
95    let flex_rank_str = if flex_rank["lp"].as_i64().unwrap_or(0) > 0 {
96        if !flex_rank["division"].as_str().unwrap_or("").is_empty() {
97            format!(
98                "**{} {}** ({} LP)",
99                flex_emoji,
100                flex_rank["division"].as_str().unwrap(),
101                flex_rank["lp"].as_i64().unwrap()
102            )
103        } else {
104            format!(
105                "**{}** ({} LP)",
106                flex_emoji,
107                flex_rank["lp"].as_i64().unwrap()
108            )
109        }
110    } else {
111        format!("**{}**", flex_emoji)
112    };
113
114    // Construction de l'embed
115    let embed = CreateEmbed::default()
116        .title(format!("📊 Stats for **{}#{}**", modal_data.game_name, modal_data.tag_line))
117        .color(0x00ff00)
118        .field("**Solo/Duo Rank**", solo_rank_str, false)
119        .field("🏆 **Wins**", format!("**{}**", solo_rank["wins"].as_i64().unwrap_or(-1)), true)
120        .field("❌ **Losses**", format!("**{}**", solo_rank["losses"].as_i64().unwrap_or(-1)), true)
121        .field(
122            "📊 **Winrate**",
123            format!("**{:.2}%**", solo_rank["winrate"].as_f64().unwrap_or(-1.0)),
124            true
125        )
126        .field("**Flex Rank**", flex_rank_str, false)
127        .field("🏆 **Wins**", format!("**{}**", flex_rank["wins"].as_i64().unwrap_or(-1)), true)
128        .field("❌ **Losses**", format!("**{}**", flex_rank["losses"].as_i64().unwrap_or(-1)), true)
129        .field(
130            "📊 **Winrate**",
131            format!("**{:.2}%**", flex_rank["winrate"].as_f64().unwrap_or(-1.0)),
132            true
133        )
134        .field("💥 **Top Champions**", champions_info, false)
135        .field(
136            "📜 **Match Details**",
137            if match_details.is_empty() {
138                "No match found on Normal and ranked game".to_string()
139            } else {
140                match_details
141                    .iter()
142                    .map(|match_detail| {
143                        format!(
144                            "{} - **{}**, {} ({}):\nK/D/A: **{}** | **{} CS** | Duration: **{}**\n⏳ Played: **{}**\n\n",
145                            match_detail.get("Result").unwrap().as_str().unwrap(),
146                            match_detail.get("champion_name").unwrap().as_str().unwrap(),
147                            match_detail.get("time_elapsed").unwrap().as_str().unwrap(),
148                            match_detail.get("game_type").unwrap().as_str().unwrap(),
149                            match_detail.get("K/D/A").unwrap().as_str().unwrap(),
150                            match_detail.get("Farm").unwrap().as_u64().unwrap(),
151                            match_detail.get("Duration").unwrap().as_str().unwrap(),
152                            match_detail.get("time_elapsed").unwrap().as_str().unwrap()
153                        )
154                    })
155                    .collect::<String>()
156            },
157            false
158        )
159        .footer(CreateEmbedFooter::new("This message will be deleted in 60 seconds."))
160        .thumbnail("https://i.postimg.cc/9fKf2tYp/Logo.png");
161
162    Ok(embed)
163}
164
165/// ⚙️ **Function**: Creates an embed displaying an error message for Discord interactions.
166///
167/// This function constructs a Discord embed message that displays a given error message in a formatted way.
168/// The embed is styled with a red color to indicate an error and includes a default title of "Error".
169/// The embed is returned as part of a `CreateReply`, which can be sent to a Discord channel.
170///
171/// # Parameters:
172/// - `error_message`: A string slice containing the error message to be displayed in the embed's description.
173///   This message is intended to provide feedback to the user, typically in case of API errors, invalid inputs,
174///   or other issues encountered during the bot's execution.
175///
176/// # Returns:
177/// - `CreateReply`: A response object that includes the error embed. This is ready to be sent to a Discord channel.
178///
179/// # ⚠️ Notes:
180/// - The embed's color is set to red (`0xff0000`) to visually signify an error.
181/// - The title of the embed is always set to "Error", and the provided `error_message` is used in the description.
182/// - The function is primarily used to provide user-friendly error messages in response to invalid inputs
183///   or issues in API calls.
184///
185/// # Example:
186/// ```rust
187/// let error_reply = create_embed_error("Failed to fetch data from the Riot API.");
188/// ctx.send(error_reply).await?;
189/// ```
190///
191/// The resulting embed message will look like this:
192/// ```text
193/// ❌ **Error**
194/// Failed to fetch data from the Riot API.
195/// ```
196pub fn create_embed_error(error_message: &str) -> CreateReply {
197    let embed: CreateEmbed = CreateEmbed::default()
198        .title("Error")
199        .description(error_message)
200        .color(0xff0000)
201        .footer(CreateEmbedFooter::new(
202            "This message will be deleted in 60 seconds.",
203        ))
204        .thumbnail("https://i.postimg.cc/9fKf2tYp/Logo.png");
205    CreateReply {
206        embeds: vec![embed],
207        ..Default::default()
208    }
209}
210
211/// ⚙️ **Function**: Creates a success embed reply for Discord messages.
212///
213/// This function generates a Discord embed with the title "Success", a description provided by the `success_message` parameter,
214/// a green color to indicate success, and a footer notifying that the message will be deleted in 60 seconds. It returns a
215/// `CreateReply` containing the embed, suitable for sending as a response to a Discord interaction or message.
216///
217/// # Parameters:
218/// - `success_message`: A string slice that holds the success message to be displayed in the embed's description.
219///
220/// # Returns:
221/// - `CreateReply`: A Discord reply containing the constructed success embed.
222///
223/// # ⚠️ Notes:
224/// - The embed's footer is in French: "This message will be deleted in 60 seconds." ("This message will be deleted in 60 seconds.").
225/// - The title "Success" appears to have a typo and might be intended as "Success".
226/// - The embed uses a green color (`0x00ff00`) to visually indicate a successful operation.
227///
228/// # Example:
229/// ```rust
230/// let reply = create_embed_success("Operation completed successfully!");
231/// // Use `reply` to send the embed in a Discord channel
232/// ```
233pub fn create_embed_success(success_message: &str) -> CreateReply {
234    let embed: CreateEmbed = CreateEmbed::default()
235        .title("Success")
236        .description(success_message)
237        .color(0x00ff00)
238        .footer(CreateEmbedFooter::new(
239            "This message will be deleted in 60 seconds.",
240        ))
241        .thumbnail("https://i.postimg.cc/9fKf2tYp/Logo.png");
242    CreateReply {
243        embeds: vec![embed],
244        ..Default::default()
245    }
246}
247
248/// ⚙️ **Function**: Schedules the deletion of a Discord message after a delay.
249///
250/// This function delays the deletion of a Discord message by 60 seconds. After the delay, the function attempts
251/// to delete the message from the channel. It ensures that messages sent by the bot (e.g., error messages or
252/// status updates) are automatically removed after a certain time to keep the chat clean.
253///
254/// # Parameters:
255/// - `sent_message`: A `ReplyHandle` representing the message to be deleted. This handle provides access to the
256///   message object, allowing the function to delete it once the delay has passed.
257/// - `ctx`: The application context, which provides access to Discord methods (such as message deletion)
258///   and other necessary data like API keys.
259///
260/// # Returns:
261/// - `Result<(), Error>`: If successful, returns `Ok(())`. If an error occurs while fetching the message
262///   or deleting it, returns an `Error`.
263///
264/// # ⚠️ Notes:
265/// - The function uses `tokio::time::sleep` to pause execution for 60 seconds before attempting to delete the message.
266/// - If the message cannot be fetched (e.g., due to permissions or being deleted manually), the deletion attempt will silently fail.
267/// - This function is typically used in scenarios where temporary messages (like errors or status updates)
268///   need to be cleaned up automatically after a short period.
269///
270/// # Example:
271/// ```rust
272/// schedule_message_deletion(sent_message, ctx).await?;
273/// ```
274///
275/// After 60 seconds, the message will be deleted from the Discord channel.
276pub async fn schedule_message_deletion(
277    sent_message: ReplyHandle<'_>,
278    ctx: poise::ApplicationContext<'_, Data, Error>,
279) -> Result<(), Error> {
280    sleep(Duration::from_secs(60)).await;
281    if let Ok(sent_msg) = sent_message.message().await {
282        sent_msg.delete(&ctx.serenity_context().http).await?;
283    }
284    Ok(())
285}