rust_commit_tracker/services/
discord.rs

1use crate::core::Config;
2use crate::models::{DiscordEmbed, EmbedAuthor, EmbedData, EmbedField, EmbedFooter};
3use crate::services::scraper::CommitResult;
4use chrono;
5use std::error::Error;
6
7pub struct DiscordNotifier {
8    client: reqwest::Client,
9    config: Config,
10}
11
12impl DiscordNotifier {
13    pub fn new(config: Config) -> Self {
14        Self {
15            client: reqwest::Client::new(),
16            config,
17        }
18    }
19
20    pub async fn send_commit_notification(
21        &self,
22        result: &CommitResult,
23    ) -> Result<(), Box<dyn Error>> {
24        let embed = self.build_embed(result);
25
26        let response = self
27            .client
28            .post(&self.config.discord.webhook_url)
29            .header("Content-Type", "application/json")
30            .json(&embed)
31            .send()
32            .await?;
33
34        if !response.status().is_success() {
35            return Err(
36                format!("Discord webhook failed with status: {}", response.status()).into(),
37            );
38        }
39
40        Ok(())
41    }
42
43    fn build_embed(&self, result: &CommitResult) -> DiscordEmbed {
44        let commit = &result.commit;
45
46        DiscordEmbed {
47            embeds: vec![EmbedData {
48                title: "🔧 New Rust Commit".to_string(),
49                description: format!("```\n{}\n```", commit.message),
50                color: self.config.rust_color(),
51                author: EmbedAuthor {
52                    name: commit.author().to_string(),
53                    url: self.config.monitoring.commits_url.clone(),
54                    icon_url: commit.avatar_url().to_string(),
55                },
56                fields: vec![
57                    EmbedField {
58                        name: "📁 Repository".to_string(),
59                        value: format!("`{}`", commit.repo),
60                        inline: true,
61                    },
62                    EmbedField {
63                        name: "🌿 Branch".to_string(),
64                        value: format!("`{}`", commit.branch),
65                        inline: true,
66                    },
67                    EmbedField {
68                        name: "🔗 Changeset".to_string(),
69                        value: format!("[`{}`]({})", commit.changeset, commit.link()),
70                        inline: true,
71                    },
72                ],
73                footer: EmbedFooter {
74                    text: format!(
75                        "{} • Commit {} of {}",
76                        self.config.discord.bot_name,
77                        self.format_number(result.total_commits - result.position + 1),
78                        self.format_number(result.total_commits)
79                    ),
80                    icon_url: self.config.discord.bot_avatar_url.clone(),
81                },
82                timestamp: chrono::Utc::now().to_rfc3339(),
83            }],
84        }
85    }
86
87    fn format_number(&self, num: u32) -> String {
88        // Add commas to large numbers for readability
89        let num_str = num.to_string();
90        let chars: Vec<char> = num_str.chars().collect();
91        let mut result = String::new();
92
93        for (i, &ch) in chars.iter().enumerate() {
94            if i > 0 && (chars.len() - i) % 3 == 0 {
95                result.push(',');
96            }
97            result.push(ch);
98        }
99
100        result
101    }
102}