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]
145async 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- }
0 commit comments