@@ -3,6 +3,7 @@ mod request;
33use std:: { borrow:: Cow , collections:: VecDeque , fmt, future:: Future , pin:: Pin , time:: SystemTime } ;
44
55use anyhow:: { anyhow, bail, ensure} ;
6+ use http:: Uri ;
67use request:: * ;
78use serde:: Deserialize ;
89use serde_json as json;
@@ -16,8 +17,9 @@ use crate::{
1617 platform:: { PlatformMetadata , PlatformTrait } ,
1718 secret_enum, serde_impl_default_for,
1819 source:: {
19- LiveStatus , LiveStatusKind , Notification , NotificationKind , Post , PostAttachment , PostUrl ,
20- PostsRef , RepostFrom , StatusSource ,
20+ DocumentRef , FileRef , LiveStatus , LiveStatusKind , Notification , NotificationKind ,
21+ PlaybackFormat , PlaybackRef , Post , PostAttachment , PostUrl , PostsRef , RepostFrom ,
22+ StatusSource ,
2123 } ,
2224} ;
2325
@@ -26,6 +28,8 @@ pub struct ConfigGlobal {
2628 #[ serde( flatten) ]
2729 pub token : Option < ConfigToken > ,
2830 #[ serde( default ) ]
31+ pub api_server : Option < ConfigApiServer > ,
32+ #[ serde( default ) ]
2933 pub experimental : ConfigExperimental ,
3034}
3135
@@ -42,6 +46,17 @@ impl config::Validator for ConfigGlobal {
4246 }
4347}
4448
49+ #[ derive( Clone , Debug , PartialEq , Deserialize ) ]
50+ #[ serde( untagged) ]
51+ pub enum ConfigApiServer {
52+ Url ( #[ serde( with = "http_serde::uri" ) ] Uri ) ,
53+ UrlOpts {
54+ #[ serde( with = "http_serde::uri" ) ]
55+ url : Uri ,
56+ as_necessary : bool ,
57+ } ,
58+ }
59+
4560#[ derive( Clone , Debug , PartialEq , Deserialize ) ]
4661pub struct ConfigExperimental {
4762 #[ deprecated = "enabled by default" ]
@@ -213,6 +228,12 @@ impl Notifier {
213228 }
214229 NotificationKind :: Posts ( posts) => self . notify_posts ( posts, notification. source ) . await ,
215230 NotificationKind :: Log ( message) => self . notify_log ( message) . await ,
231+ NotificationKind :: Playback ( playback) => {
232+ self . notify_playback ( playback, notification. source ) . await
233+ }
234+ NotificationKind :: Document ( document) => {
235+ self . notify_document ( document, notification. source ) . await
236+ }
216237 }
217238 }
218239
@@ -566,6 +587,110 @@ impl Notifier {
566587
567588 Ok ( ( ) )
568589 }
590+
591+ // TODO: Parallel notify
592+ async fn notify_playback (
593+ & self ,
594+ playback : & PlaybackRef < ' _ > ,
595+ source : & StatusSource ,
596+ ) -> anyhow:: Result < ( ) > {
597+ if !self . params . notifications . playback {
598+ info ! ( "playback notification is disabled, skip notifying" ) ;
599+ return Ok ( ( ) ) ;
600+ }
601+
602+ const FORMAT : PlaybackFormat = PlaybackFormat :: Mp4 ;
603+
604+ let file = playback. get ( FORMAT ) . await ?;
605+
606+ let token = self . token ( ) ?;
607+
608+ // Send "uploading" message
609+
610+ let resp = Request :: new ( & token)
611+ . send_message ( & self . params . chat , make_file_text ( "⏳" , & file, source) )
612+ . thread_id_opt ( self . params . thread_id )
613+ . link_preview ( LinkPreview :: Disabled )
614+ // .disable_notification() // TODO: Make it configurable
615+ . send ( )
616+ . await
617+ . map_err ( |err| anyhow ! ( "failed to send request to Telegram: {err}" ) ) ?;
618+ ensure ! (
619+ resp. ok,
620+ "response contains error, description '{}'" ,
621+ resp. description
622+ . unwrap_or_else( || "*no description*" . into( ) )
623+ ) ;
624+
625+ // Edit the media
626+
627+ trace ! ( "uploading playback to Telegram '{file}'" ) ;
628+
629+ let resp = Request :: new ( & token)
630+ . edit_message_media (
631+ & self . params . chat ,
632+ resp. result . unwrap ( ) . message_id ,
633+ Media :: Video ( MediaVideo {
634+ input : MediaInput :: Memory {
635+ data : file. data . clone ( ) ,
636+ filename : Some ( & file. name ) ,
637+ } ,
638+ resolution : Some ( file. resolution ) ,
639+ has_spoiler : false ,
640+ } ) ,
641+ )
642+ . text ( make_file_text ( "🎥" , & file, source) )
643+ . prefer_self_host ( )
644+ . send ( )
645+ . await
646+ . map_err ( |err| anyhow ! ( "failed to send request to Telegram: {err}" ) ) ?;
647+ ensure ! (
648+ resp. ok,
649+ "response contains error, description '{}'" ,
650+ resp. description
651+ . unwrap_or_else( || "*no description*" . into( ) )
652+ ) ;
653+
654+ Ok ( ( ) )
655+ }
656+
657+ async fn notify_document (
658+ & self ,
659+ document : & DocumentRef < ' _ > ,
660+ source : & StatusSource ,
661+ ) -> anyhow:: Result < ( ) > {
662+ if !self . params . notifications . document {
663+ info ! ( "document notification is disabled, skip notifying" ) ;
664+ return Ok ( ( ) ) ;
665+ }
666+
667+ let token = self . token ( ) ?;
668+
669+ let resp = Request :: new ( & token)
670+ . send_document (
671+ & self . params . chat ,
672+ MediaDocument {
673+ input : MediaInput :: Memory {
674+ data : document. file . data . clone ( ) ,
675+ filename : Some ( & document. file . name ) ,
676+ } ,
677+ } ,
678+ )
679+ . text ( make_file_text ( "📊" , & document. file , source) )
680+ . thread_id_opt ( self . params . thread_id )
681+ // .disable_notification() // TODO: Make it configurable
682+ . send ( )
683+ . await
684+ . map_err ( |err| anyhow ! ( "failed to send request to Telegram: {err}" ) ) ?;
685+ ensure ! (
686+ resp. ok,
687+ "response contains error, description '{}'" ,
688+ resp. description
689+ . unwrap_or_else( || "*no description*" . into( ) )
690+ ) ;
691+
692+ Ok ( ( ) )
693+ }
569694}
570695
571696fn make_live_text < ' a > (
@@ -597,6 +722,15 @@ fn make_live_text<'a>(
597722 Text :: link ( text, & live_status. live_url )
598723}
599724
725+ fn make_file_text < ' a > ( emoji : & ' a str , file : & FileRef < ' a > , source : & ' a StatusSource ) -> Text < ' a > {
726+ Text :: plain ( format ! (
727+ "[{}] {emoji} {} ({})" ,
728+ source. platform. display_name,
729+ file. name,
730+ humansize:: format_size( file. size, humansize:: BINARY )
731+ ) )
732+ }
733+
600734struct CurrentLive {
601735 start_time : SystemTime ,
602736 message_id : i64 ,
0 commit comments