11//! Admin jobs: copy Telegram streams to the backup channel or restore DB rows from it.
22
33use async_trait:: async_trait;
4+ use std:: collections:: HashMap ;
5+ use std:: sync:: Arc ;
46use tracing:: info;
57
8+ use grammers_client:: Client ;
9+ use grammers_session:: types:: PeerRef ;
10+
611use crate :: {
712 db:: {
813 telegram:: { TelegramStreamBackupRow , list_streams_for_backup_store} ,
@@ -12,9 +17,10 @@ use crate::{
1217 error:: JobError ,
1318 handler:: { JobCtx , JobHandler } ,
1419 } ,
20+ scrapers:: telegram_clients:: is_auth_key_duplicated,
1521 services:: telegram_backup:: {
16- BackupBatchMetrics , resolve_mtproto_client , restore_stream_from_backup_message ,
17- store_stream_to_backup,
22+ BackupBatchMetrics , resolve_bot_mtproto_client , resolve_mtproto_client ,
23+ restore_stream_from_backup_message , store_stream_to_backup,
1824 } ,
1925 services:: telegram_peer,
2026} ;
@@ -71,10 +77,42 @@ fn default_capture_file_id() -> bool {
7177 true
7278}
7379
80+ async fn load_user_session (
81+ ctx : & JobCtx ,
82+ preferred : Option < UserId > ,
83+ ) -> ( Option < Arc < Client > > , Option < HashMap < i64 , PeerRef > > ) {
84+ for attempt in 0 ..2 {
85+ let session = resolve_mtproto_client ( & ctx. state , preferred) . await . ok ( ) ;
86+ let Some ( ( user_id, client) ) = session else {
87+ return ( None , None ) ;
88+ } ;
89+
90+ let ( dialog_peers, dialog_error) =
91+ telegram_peer:: load_dialog_peer_map ( client. as_ref ( ) ) . await ;
92+ if let Some ( err) = dialog_error. as_deref ( )
93+ && attempt == 0
94+ && is_auth_key_duplicated ( err)
95+ {
96+ tracing:: warn!(
97+ "telegram_backup_store: AUTH_KEY_DUPLICATED for user {} — recycling client" ,
98+ user_id. 0
99+ ) ;
100+ ctx. state . telegram_clients . invalidate ( user_id) . await ;
101+ telegram_peer:: invalidate_dialog_peer_cache ( user_id) . await ;
102+ continue ;
103+ }
104+
105+ return ( Some ( client) , Some ( dialog_peers) ) ;
106+ }
107+
108+ ( None , None )
109+ }
110+
74111async fn run_backup_store_batch (
75112 ctx : & JobCtx ,
76113 args : & TelegramBackupStoreArgs ,
77- client : & grammers_client:: Client ,
114+ user_client : Option < & Client > ,
115+ user_dialog_peers : Option < & HashMap < i64 , PeerRef > > ,
78116 after_id : i32 ,
79117) -> Result < ( BackupBatchMetrics , i32 ) , JobError > {
80118 let batch_size = args. batch_size . clamp ( 1 , 200 ) ;
@@ -86,7 +124,6 @@ async fn run_backup_store_batch(
86124 return Ok ( ( BackupBatchMetrics :: default ( ) , after_id) ) ;
87125 }
88126
89- let ( dialog_peers, _) = telegram_peer:: load_dialog_peer_map ( client) . await ;
90127 let mut metrics = BackupBatchMetrics :: default ( ) ;
91128 let mut last_id = after_id;
92129
@@ -96,7 +133,15 @@ async fn run_backup_store_batch(
96133 }
97134 last_id = row. id ;
98135 metrics. processed += 1 ;
99- match store_one ( ctx, client, & dialog_peers, & row, args. capture_file_id ) . await {
136+ match store_one (
137+ ctx,
138+ user_client,
139+ user_dialog_peers,
140+ & row,
141+ args. capture_file_id ,
142+ )
143+ . await
144+ {
100145 Ok ( true ) => metrics. stored += 1 ,
101146 Ok ( false ) => metrics. skipped += 1 ,
102147 Err ( e) => {
@@ -111,12 +156,19 @@ async fn run_backup_store_batch(
111156
112157async fn store_one (
113158 ctx : & JobCtx ,
114- client : & grammers_client :: Client ,
115- dialog_peers : & std :: collections :: HashMap < i64 , grammers_session :: types :: PeerRef > ,
159+ user_client : Option < & Client > ,
160+ user_dialog_peers : Option < & HashMap < i64 , PeerRef > > ,
116161 row : & TelegramStreamBackupRow ,
117162 capture_file_id : bool ,
118163) -> Result < bool , String > {
119- store_stream_to_backup ( & ctx. state , client, dialog_peers, row, capture_file_id) . await ?;
164+ store_stream_to_backup (
165+ & ctx. state ,
166+ user_client,
167+ user_dialog_peers,
168+ row,
169+ capture_file_id,
170+ )
171+ . await ?;
120172 Ok ( true )
121173}
122174
@@ -127,9 +179,11 @@ impl JobHandler for TelegramBackupStore {
127179 type Args = TelegramBackupStoreArgs ;
128180
129181 async fn run ( & self , args : Self :: Args , ctx : JobCtx ) -> Result < ( ) , JobError > {
130- if !ctx. state . telegram_clients . api_configured ( ) {
182+ if ctx. state . config . telegram_bot_token . is_none ( )
183+ && !ctx. state . telegram_clients . api_configured ( )
184+ {
131185 return Err ( JobError :: other (
132- "Telegram API credentials are not configured " ,
186+ "Configure TELEGRAM_BOT_TOKEN or Telegram API credentials with a scraping session " ,
133187 ) ) ;
134188 }
135189 if ctx
@@ -146,9 +200,7 @@ impl JobHandler for TelegramBackupStore {
146200 }
147201
148202 let preferred = args. mediafusion_user_id . map ( UserId ) ;
149- let ( _user_id, client) = resolve_mtproto_client ( & ctx. state , preferred)
150- . await
151- . map_err ( JobError :: other) ?;
203+ let ( user_client, user_dialog_peers) = load_user_session ( & ctx, preferred) . await ;
152204
153205 let mut after_id = args. after_id ;
154206 let mut totals = BackupBatchMetrics :: default ( ) ;
@@ -159,8 +211,14 @@ impl JobHandler for TelegramBackupStore {
159211 return Err ( JobError :: Cancelled ) ;
160212 }
161213
162- let ( batch, last_id) =
163- run_backup_store_batch ( & ctx, & args, client. as_ref ( ) , after_id) . await ?;
214+ let ( batch, last_id) = run_backup_store_batch (
215+ & ctx,
216+ & args,
217+ user_client. as_deref ( ) ,
218+ user_dialog_peers. as_ref ( ) ,
219+ after_id,
220+ )
221+ . await ?;
164222 if batch. processed == 0 {
165223 break ;
166224 }
@@ -197,6 +255,9 @@ impl JobHandler for TelegramBackupRestore {
197255 type Args = TelegramBackupRestoreArgs ;
198256
199257 async fn run ( & self , args : Self :: Args , ctx : JobCtx ) -> Result < ( ) , JobError > {
258+ if ctx. state . config . telegram_bot_token . is_none ( ) {
259+ return Err ( JobError :: other ( "TELEGRAM_BOT_TOKEN is not configured" ) ) ;
260+ }
200261 if !ctx. state . telegram_clients . api_configured ( ) {
201262 return Err ( JobError :: other (
202263 "Telegram API credentials are not configured" ,
@@ -211,21 +272,21 @@ impl JobHandler for TelegramBackupRestore {
211272 . filter ( |s| !s. is_empty ( ) )
212273 . ok_or_else ( || JobError :: other ( "TELEGRAM_BACKUP_CHANNEL_ID is not configured" ) ) ?;
213274
214- let preferred = args. mediafusion_user_id . map ( UserId ) ;
215- let ( _user_id , client) = resolve_mtproto_client ( & ctx. state , preferred )
275+ let _ = args. mediafusion_user_id ;
276+ let client = resolve_bot_mtproto_client ( & ctx. state )
216277 . await
217278 . map_err ( JobError :: other) ?;
218279
219- let ( dialog_peers, _) = telegram_peer:: load_dialog_peer_map ( & client) . await ;
280+ let ( dialog_peers, _) = telegram_peer:: load_dialog_peer_map ( client. as_ref ( ) ) . await ;
220281 let ( _, backup_peer_ref) = telegram_peer:: resolve_channel_peer (
221- & client,
282+ client. as_ref ( ) ,
222283 backup_channel,
223284 & dialog_peers,
224285 )
225286 . await
226287 . ok_or_else ( || {
227288 JobError :: other ( format ! (
228- "backup channel {backup_channel} is not accessible with the scraping session "
289+ "backup channel {backup_channel} is not accessible to the bot — add the bot as admin with read history "
229290 ) )
230291 } ) ?;
231292
0 commit comments