@@ -217,19 +217,136 @@ impl TelegramProvider {
217217 Ok ( ( ) )
218218 }
219219
220+ /// Send a message as a reply to another message
221+ async fn send_reply ( & self , reply_to_message_id : i64 , text : & str ) -> Result < i64 > {
222+ #[ derive( Serialize ) ]
223+ struct Req {
224+ chat_id : i64 ,
225+ text : String ,
226+ parse_mode : String ,
227+ reply_to_message_id : i64 ,
228+ }
229+ let resp: TelegramResponse < Message > = self
230+ . client
231+ . post ( format ! ( "{}/sendMessage" , self . base_url) )
232+ . json ( & Req {
233+ chat_id : self . config . chat_id ,
234+ text : text. to_string ( ) ,
235+ parse_mode : "HTML" . to_string ( ) ,
236+ reply_to_message_id,
237+ } )
238+ . send ( )
239+ . await ?
240+ . json ( )
241+ . await ?;
242+ let msg_id = resp. result . map_or ( 0 , |m| m. message_id ) ;
243+ Ok ( msg_id)
244+ }
245+
246+ /// Send a standalone notice message
247+ async fn send_notice ( & self , text : & str ) -> Result < ( ) > {
248+ #[ derive( Serialize ) ]
249+ struct Req {
250+ chat_id : i64 ,
251+ text : String ,
252+ parse_mode : String ,
253+ }
254+ self . client
255+ . post ( format ! ( "{}/sendMessage" , self . base_url) )
256+ . json ( & Req {
257+ chat_id : self . config . chat_id ,
258+ text : text. to_string ( ) ,
259+ parse_mode : "HTML" . to_string ( ) ,
260+ } )
261+ . send ( )
262+ . await ?;
263+ Ok ( ( ) )
264+ }
265+
266+ /// After reject, send a follow-up reply and wait for feedback text
267+ async fn wait_for_reject_feedback (
268+ & self ,
269+ original_message_id : i64 ,
270+ wait_secs : u64 ,
271+ mut offset : Option < i64 > ,
272+ ) -> Result < Option < String > > {
273+ if wait_secs == 0 {
274+ return Ok ( None ) ;
275+ }
276+
277+ // Send follow-up as reply to the original request message
278+ // Send follow-up as reply to the original request message
279+ self . send_reply ( original_message_id, self . messages . reject_feedback_prompt )
280+ . await
281+ . ok ( ) ;
282+
283+ let deadline = tokio:: time:: Instant :: now ( ) + Duration :: from_secs ( wait_secs) ;
284+
285+ loop {
286+ if tokio:: time:: Instant :: now ( ) >= deadline {
287+ return Ok ( None ) ;
288+ }
289+
290+ let remaining = deadline - tokio:: time:: Instant :: now ( ) ;
291+ let poll_timeout = remaining. min ( Duration :: from_secs ( 10 ) ) ;
292+
293+ let mut url = format ! (
294+ "{}/getUpdates?timeout={}&allowed_updates=[\" message\" ]" ,
295+ self . base_url,
296+ poll_timeout. as_secs( )
297+ ) ;
298+ if let Some ( off) = offset {
299+ url. push_str ( & format ! ( "&offset={off}" ) ) ;
300+ }
301+
302+ let resp: TelegramResponse < Vec < Update > > =
303+ self . client . get ( & url) . send ( ) . await ?. json ( ) . await ?;
304+
305+ let updates = match resp. result {
306+ Some ( u) => u,
307+ None => continue ,
308+ } ;
309+
310+ for update in updates {
311+ offset = Some ( update. update_id + 1 ) ;
312+
313+ if let Some ( msg) = update. message {
314+ if msg. chat . id != self . config . chat_id {
315+ continue ;
316+ }
317+ let user_id = msg. from . as_ref ( ) . map_or ( 0 , |u| u. id ) ;
318+ if !self . is_trusted ( user_id) {
319+ continue ;
320+ }
321+ // Accept any text message from trusted user in this chat
322+ // (reply to original, reply to follow-up, or direct message)
323+ if msg. text . is_some ( ) {
324+ return Ok ( msg. text . clone ( ) ) ;
325+ }
326+ }
327+ }
328+
329+ tokio:: time:: sleep ( POLL_INTERVAL ) . await ;
330+ }
331+ }
332+
220333 async fn poll_for_response (
221334 & self ,
222335 sent_message_id : i64 ,
223336 timeout : Duration ,
224- title : & str ,
337+ request : & FeedbackRequest ,
225338 ) -> Result < FeedbackResponse > {
226339 let deadline = tokio:: time:: Instant :: now ( ) + timeout;
227340 let mut offset: Option < i64 > = None ;
228341
229342 loop {
230343 if tokio:: time:: Instant :: now ( ) >= deadline {
231344 info ! ( "Timeout reached, no response received" ) ;
232- return Ok ( FeedbackResponse :: timeout ( title) ) ;
345+ self . edit_message_reply_markup ( self . config . chat_id , sent_message_id)
346+ . await
347+ . ok ( ) ;
348+ self . send_notice ( self . messages . timeout_notice ) . await . ok ( ) ;
349+ return Ok ( FeedbackResponse :: timeout ( & request. title ) ) ;
233350 }
234351
235352 let remaining = deadline - tokio:: time:: Instant :: now ( ) ;
@@ -300,13 +417,31 @@ impl TelegramProvider {
300417 "Response received"
301418 ) ;
302419
420+ // For reject: prompt for optional feedback
421+ let feedback = if decision == Decision :: Rejected {
422+ let fb = self
423+ . wait_for_reject_feedback (
424+ sent_message_id,
425+ request. reject_feedback_timeout_secs ,
426+ offset,
427+ )
428+ . await
429+ . unwrap_or ( None ) ;
430+ if fb. is_some ( ) {
431+ info ! ( feedback = ?fb, "Reject feedback received" ) ;
432+ }
433+ fb
434+ } else {
435+ None
436+ } ;
437+
303438 return Ok ( FeedbackResponse {
304439 decision,
305440 user : cb. from . display_name ( ) ,
306441 user_id : cb. from . id ,
307- feedback : None ,
442+ feedback,
308443 timestamp : Utc :: now ( ) ,
309- request_title : title. to_string ( ) ,
444+ request_title : request . title . clone ( ) ,
310445 } ) ;
311446 }
312447
@@ -350,7 +485,7 @@ impl TelegramProvider {
350485 user_id,
351486 feedback : feedback_text,
352487 timestamp : Utc :: now ( ) ,
353- request_title : title. to_string ( ) ,
488+ request_title : request . title . clone ( ) ,
354489 } ) ;
355490 }
356491 }
@@ -367,7 +502,7 @@ impl Provider for TelegramProvider {
367502 async fn send_and_wait ( & self , request : & FeedbackRequest ) -> Result < FeedbackResponse > {
368503 let msg_id = self . send_message ( request) . await ?;
369504 let timeout = Duration :: from_secs ( request. timeout_secs ) ;
370- self . poll_for_response ( msg_id, timeout, & request. title ) . await
505+ self . poll_for_response ( msg_id, timeout, request) . await
371506 }
372507}
373508
0 commit comments