@@ -10,8 +10,8 @@ use teloxide::Bot;
1010use teloxide:: payloads:: setters:: * ;
1111use teloxide:: requests:: { Request , Requester } ;
1212use teloxide:: types:: {
13- ChatAction , ChatId , FileId , InputFile , MediaKind , MessageId , MessageKind , ReactionType ,
14- ReplyParameters , UpdateKind , UserId ,
13+ ChatAction , ChatId , FileId , InputFile , InputPollOption , MediaKind , MessageId , MessageKind ,
14+ ReactionType , ReplyParameters , UpdateKind , UserId ,
1515} ;
1616
1717use std:: collections:: { HashMap , VecDeque } ;
@@ -218,6 +218,11 @@ impl Messaging for TelegramAdapter {
218218 } else if let Some ( filter) = & permissions. chat_filter {
219219 // Chat filter: if configured, only allow listed group/channel chats
220220 if !filter. contains( & chat_id) {
221+ tracing:: debug!(
222+ chat_id,
223+ ?filter,
224+ "telegram message rejected by chat filter"
225+ ) ;
221226 continue ;
222227 }
223228 }
@@ -289,7 +294,7 @@ impl Messaging for TelegramAdapter {
289294 . context ( "failed to send telegram message" ) ?;
290295 }
291296 }
292- OutboundResponse :: RichMessage { text, .. } => {
297+ OutboundResponse :: RichMessage { text, poll , .. } => {
293298 self . stop_typing ( & message. conversation_id ) . await ;
294299
295300 for chunk in split_message ( & text, MAX_MESSAGE_LENGTH ) {
@@ -299,6 +304,10 @@ impl Messaging for TelegramAdapter {
299304 . await
300305 . context ( "failed to send telegram message" ) ?;
301306 }
307+
308+ if let Some ( poll_data) = poll {
309+ send_poll ( & self . bot , chat_id, & poll_data) . await ?;
310+ }
302311 }
303312 OutboundResponse :: ThreadReply {
304313 thread_name : _,
@@ -492,14 +501,18 @@ impl Messaging for TelegramAdapter {
492501 . await
493502 . context ( "failed to broadcast telegram message" ) ?;
494503 }
495- } else if let OutboundResponse :: RichMessage { text, .. } = response {
504+ } else if let OutboundResponse :: RichMessage { text, poll , .. } = response {
496505 for chunk in split_message ( & text, MAX_MESSAGE_LENGTH ) {
497506 self . bot
498507 . send_message ( chat_id, & chunk)
499508 . send ( )
500509 . await
501510 . context ( "failed to broadcast telegram message" ) ?;
502511 }
512+
513+ if let Some ( poll_data) = poll {
514+ send_poll ( & self . bot , chat_id, & poll_data) . await ?;
515+ }
503516 }
504517
505518 Ok ( ( ) )
@@ -811,6 +824,61 @@ fn build_display_name(user: &teloxide::types::User) -> String {
811824 }
812825}
813826
827+ /// Send a native Telegram poll.
828+ ///
829+ /// Telegram limits: max 12 answer options, question max 300 chars, each option
830+ /// max 100 chars. `open_period` only supports 5–600 seconds so we only set it
831+ /// when `duration_hours` converts to ≤600s; otherwise the poll stays open
832+ /// indefinitely (until manually stopped via the Telegram client).
833+ async fn send_poll ( bot : & Bot , chat_id : ChatId , poll : & crate :: Poll ) -> anyhow:: Result < ( ) > {
834+ let question = if poll. question . len ( ) > 300 {
835+ format ! ( "{}…" , & poll. question[ ..poll. question. floor_char_boundary( 299 ) ] )
836+ } else {
837+ poll. question . clone ( )
838+ } ;
839+
840+ let options: Vec < InputPollOption > = poll
841+ . answers
842+ . iter ( )
843+ . take ( 12 )
844+ . map ( |answer| {
845+ let text = if answer. len ( ) > 100 {
846+ format ! ( "{}…" , & answer[ ..answer. floor_char_boundary( 99 ) ] )
847+ } else {
848+ answer. clone ( )
849+ } ;
850+ InputPollOption :: new ( text)
851+ } )
852+ . collect ( ) ;
853+
854+ if options. len ( ) < 2 {
855+ anyhow:: bail!( "telegram polls require at least 2 answer options" ) ;
856+ }
857+
858+ let mut request = bot
859+ . send_poll ( chat_id, question, options)
860+ . is_anonymous ( false ) ;
861+
862+ // Telegram's open_period only supports 5–600 seconds. Apply it when the
863+ // requested duration fits; otherwise leave unset so the poll stays open
864+ // indefinitely.
865+ let duration_secs = poll. duration_hours . saturating_mul ( 3600 ) ;
866+ if ( 5 ..=600 ) . contains ( & duration_secs) {
867+ request = request. open_period ( duration_secs as u16 ) ;
868+ }
869+
870+ if poll. allow_multiselect {
871+ request = request. allows_multiple_answers ( true ) ;
872+ }
873+
874+ request
875+ . send ( )
876+ . await
877+ . context ( "failed to send telegram poll" ) ?;
878+
879+ Ok ( ( ) )
880+ }
881+
814882/// Split a message into chunks that fit within Telegram's character limit.
815883/// Tries to split at newlines, then spaces, then hard-cuts.
816884fn split_message ( text : & str , max_len : usize ) -> Vec < String > {
0 commit comments