Skip to content

Commit 12e8d7e

Browse files
committed
feat: implement commit tracking and Discord notifications
- Added core modules for configuration, commit tracking, and Discord notifications. - Introduced models for commit information and Discord embed structure. - Created services for scraping commit data and sending notifications. - Refactored main application logic to utilize the new tracking system.
1 parent d694928 commit 12e8d7e

11 files changed

Lines changed: 366 additions & 192 deletions

File tree

src/core/config.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#[derive(Debug, Clone)]
2+
pub struct Config {
3+
pub discord_webhook_url: String,
4+
pub commits_url: String,
5+
pub check_interval_secs: u64,
6+
pub rust_color: u32,
7+
pub footer_icon_url: String,
8+
}
9+
10+
impl Config {
11+
pub fn new() -> Self {
12+
Self {
13+
discord_webhook_url: "https://discord.com/api/webhooks/1377062435000680469/p8IPNirCl90kWHSpmX-YMEDxeNKdaGOUtofNY_jSX_B-w_3vMSXymIaKuvVVP71Xtlnq".to_string(),
14+
commits_url: "https://commits.facepunch.com/r/rust_reboot".to_string(),
15+
check_interval_secs: 50,
16+
rust_color: 0xCD412B,
17+
footer_icon_url: "https://i.imgur.com/on47Qk9.png".to_string(),
18+
}
19+
}
20+
}
21+
22+
impl Default for Config {
23+
fn default() -> Self {
24+
Self::new()
25+
}
26+
}

src/core/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub mod config;
2+
pub mod tracker;
3+
4+
pub use config::*;
5+
pub use tracker::*;

src/core/tracker.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use crate::core::Config;
2+
use crate::services::{CommitScraper, DiscordNotifier};
3+
use log::{info, error};
4+
use std::error::Error;
5+
use std::time::Duration;
6+
use tokio::time::sleep;
7+
8+
pub struct CommitTracker {
9+
config: Config,
10+
scraper: CommitScraper,
11+
notifier: DiscordNotifier,
12+
last_commit_id: i32,
13+
}
14+
15+
impl CommitTracker {
16+
pub fn new() -> Self {
17+
let config = Config::new();
18+
let scraper = CommitScraper::new();
19+
let notifier = DiscordNotifier::new(config.clone());
20+
21+
Self {
22+
config,
23+
scraper,
24+
notifier,
25+
last_commit_id: 0,
26+
}
27+
}
28+
29+
pub async fn start(&mut self) -> Result<(), Box<dyn Error>> {
30+
info!("🚀 Rust commit tracker started - monitoring Facepunch commits");
31+
32+
loop {
33+
if let Err(e) = self.check_for_new_commits().await {
34+
error!("❌ {}", e);
35+
}
36+
37+
sleep(Duration::from_secs(self.config.check_interval_secs)).await;
38+
}
39+
}
40+
41+
async fn check_for_new_commits(&mut self) -> Result<(), Box<dyn Error>> {
42+
let commit = self.scraper.fetch_latest_commit(&self.config.commits_url).await?;
43+
44+
if commit.id > self.last_commit_id {
45+
self.last_commit_id = commit.id;
46+
47+
info!("🆕 New commit #{} by {} - {}", commit.id, commit.author, commit.message);
48+
49+
self.notifier.send_commit_notification(&commit).await?;
50+
51+
info!("✅ Sent to Discord");
52+
}
53+
54+
Ok(())
55+
}
56+
}
57+
58+
impl Default for CommitTracker {
59+
fn default() -> Self {
60+
Self::new()
61+
}
62+
}

src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pub mod core;
2+
pub mod models;
3+
pub mod services;
4+
5+
pub use core::*;
6+
pub use models::*;
7+
pub use services::{CommitScraper, DiscordNotifier};

src/main.rs

Lines changed: 6 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
1-
use reqwest;
2-
use scraper::{Html, Selector};
3-
use regex::Regex;
4-
use serde_json::json;
5-
use std::time::Duration;
6-
use tokio::time::sleep;
7-
use chrono;
8-
use log::{info, error};
9-
10-
const DISCORD_WEBHOOK_URL: &str = "WEBHOOK_URL";
11-
const COMMITS_URL: &str = "https://commits.facepunch.com/r/rust_reboot";
1+
use rust_commit_tracker::CommitTracker;
2+
use log::error;
123

134
#[tokio::main]
145
async fn main() {
@@ -18,186 +9,9 @@ async fn main() {
189
.format_timestamp_secs()
1910
.init();
2011

21-
info!("🚀 Rust commit tracker started - monitoring Facepunch commits");
22-
23-
let mut last_commit_id: i32 = 0;
24-
let client = reqwest::Client::new();
25-
26-
loop {
27-
match fetch_and_process_commits(&client, &mut last_commit_id).await {
28-
Ok(_) => {},
29-
Err(e) => {
30-
error!("❌ {}", e);
31-
}
32-
}
33-
34-
sleep(Duration::from_secs(50)).await;
12+
let mut tracker = CommitTracker::new();
13+
14+
if let Err(e) = tracker.start().await {
15+
error!("❌ Fatal error: {}", e);
3516
}
3617
}
37-
38-
async fn fetch_and_process_commits(
39-
client: &reqwest::Client,
40-
last_commit_id: &mut i32,
41-
) -> Result<(), Box<dyn std::error::Error>> {
42-
// Request the page
43-
let response = client.get(COMMITS_URL).send().await?;
44-
let html_content = response.text().await?;
45-
let document = Html::parse_document(&html_content);
46-
47-
// Find the first commit
48-
let commit_selector = Selector::parse("div.commit.columns")?;
49-
let commit = document
50-
.select(&commit_selector)
51-
.next()
52-
.ok_or("No commit found")?;
53-
54-
// Extract the commit id
55-
let like_id = commit
56-
.value()
57-
.attr("like-id")
58-
.ok_or("No like-id attribute found")?;
59-
let commit_id: i32 = like_id.parse()?;
60-
61-
// If this is a new commit
62-
if commit_id > *last_commit_id {
63-
// Update the last commit id
64-
*last_commit_id = commit_id;
65-
66-
// Extract the commit details
67-
let author_selector = Selector::parse("div.author")?;
68-
let author = commit
69-
.select(&author_selector)
70-
.next()
71-
.ok_or("No author found")?
72-
.text()
73-
.collect::<String>();
74-
75-
let repo_selector = Selector::parse("span.repo")?;
76-
let repo = commit
77-
.select(&repo_selector)
78-
.next()
79-
.ok_or("No repo found")?
80-
.text()
81-
.collect::<String>();
82-
83-
let branch_selector = Selector::parse("span.branch")?;
84-
let branch = commit
85-
.select(&branch_selector)
86-
.next()
87-
.ok_or("No branch found")?
88-
.text()
89-
.collect::<String>();
90-
91-
let changeset_selector = Selector::parse("span.changeset")?;
92-
let changeset = commit
93-
.select(&changeset_selector)
94-
.next()
95-
.ok_or("No changeset found")?
96-
.text()
97-
.collect::<String>();
98-
99-
// Build changeset link using commit ID
100-
let changeset_link = format!("https://commits.facepunch.com/{}", commit_id);
101-
102-
let message_selector = Selector::parse("div.commits-message")?;
103-
let commit_message = commit
104-
.select(&message_selector)
105-
.next()
106-
.ok_or("No commit message found")?
107-
.text()
108-
.collect::<String>();
109-
110-
info!("🆕 New commit #{} by {} - {}", commit_id, author.trim(), commit_message.trim());
111-
112-
// Extract avatar URL
113-
let avatar_selector = Selector::parse("div.avatar")?;
114-
let avatar_element = commit
115-
.select(&avatar_selector)
116-
.next()
117-
.ok_or("No avatar found")?;
118-
119-
let avatar_html = avatar_element.html();
120-
let url_regex = Regex::new(r"(https?://[^\s]+)")?;
121-
let avatar_url = url_regex
122-
.find(&avatar_html)
123-
.ok_or("No URL found in avatar")?
124-
.as_str()
125-
.trim_end_matches("');")
126-
.to_string();
127-
128-
// Send to Discord webhook
129-
send_discord_webhook(
130-
client,
131-
&author,
132-
&repo,
133-
&branch,
134-
&changeset,
135-
&changeset_link,
136-
&commit_message,
137-
&avatar_url,
138-
).await?;
139-
140-
info!("✅ Sent to Discord");
141-
}
142-
143-
Ok(())
144-
}
145-
146-
async fn send_discord_webhook(
147-
client: &reqwest::Client,
148-
author: &str,
149-
repo: &str,
150-
branch: &str,
151-
changeset: &str,
152-
changeset_link: &str,
153-
commit_message: &str,
154-
avatar_url: &str,
155-
) -> Result<(), Box<dyn std::error::Error>> {
156-
let embed = json!({
157-
"embeds": [{
158-
"title": "🔧 New Rust Commit",
159-
"description": format!("```\n{}\n```", commit_message.trim()),
160-
"color": 0xCD412B, // Rust orange color
161-
"author": {
162-
"name": author.trim(),
163-
"url": "https://commits.facepunch.com/r/rust_reboot",
164-
"icon_url": avatar_url
165-
},
166-
"fields": [
167-
{
168-
"name": "📁 Repository",
169-
"value": format!("`{}`", repo.trim()),
170-
"inline": true
171-
},
172-
{
173-
"name": "🌿 Branch",
174-
"value": format!("`{}`", branch.trim()),
175-
"inline": true
176-
},
177-
{
178-
"name": "🔗 Changeset",
179-
"value": format!("[`{}`]({})", changeset.trim(), changeset_link.trim()),
180-
"inline": true
181-
}
182-
],
183-
"footer": {
184-
"text": "Facepunch Rust Commits",
185-
"icon_url": "https://i.imgur.com/on47Qk9.png"
186-
},
187-
"timestamp": chrono::Utc::now().to_rfc3339()
188-
}]
189-
});
190-
191-
let response = client
192-
.post(DISCORD_WEBHOOK_URL)
193-
.header("Content-Type", "application/json")
194-
.json(&embed)
195-
.send()
196-
.await?;
197-
198-
if !response.status().is_success() {
199-
return Err(format!("Discord webhook failed with status: {}", response.status()).into());
200-
}
201-
202-
Ok(())
203-
}

src/models/commit.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#[derive(Debug, Clone)]
2+
pub struct CommitInfo {
3+
pub id: i32,
4+
pub author: String,
5+
pub repo: String,
6+
pub branch: String,
7+
pub changeset: String,
8+
pub message: String,
9+
pub avatar_url: String,
10+
pub link: String,
11+
}

src/models/discord.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use serde::Serialize;
2+
3+
#[derive(Debug, Serialize)]
4+
pub struct DiscordEmbed {
5+
pub embeds: Vec<EmbedData>,
6+
}
7+
8+
#[derive(Debug, Serialize)]
9+
pub struct EmbedData {
10+
pub title: String,
11+
pub description: String,
12+
pub color: u32,
13+
pub author: EmbedAuthor,
14+
pub fields: Vec<EmbedField>,
15+
pub footer: EmbedFooter,
16+
pub timestamp: String,
17+
}
18+
19+
#[derive(Debug, Serialize)]
20+
pub struct EmbedAuthor {
21+
pub name: String,
22+
pub url: String,
23+
pub icon_url: String,
24+
}
25+
26+
#[derive(Debug, Serialize)]
27+
pub struct EmbedField {
28+
pub name: String,
29+
pub value: String,
30+
pub inline: bool,
31+
}
32+
33+
#[derive(Debug, Serialize)]
34+
pub struct EmbedFooter {
35+
pub text: String,
36+
pub icon_url: String,
37+
}

src/models/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub mod commit;
2+
pub mod discord;
3+
4+
pub use commit::*;
5+
pub use discord::*;

0 commit comments

Comments
 (0)