AstraOS commited on
Commit
18d5fa8
·
verified ·
1 Parent(s): d5fcd88

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +519 -40
app.py CHANGED
@@ -1,46 +1,525 @@
1
  import os
2
- import streamlit as st
3
- from telegram import Update
4
- from telegram.ext import Application, CommandHandler, ContextTypes
5
  import logging
6
- import asyncio
7
  import threading
8
- import telebot
 
 
 
 
 
9
  from fastapi import FastAPI, Request
10
- import uvicorn
11
 
12
  app = FastAPI()
13
- # Enable logging
14
- logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
15
- logger = logging.getLogger(__name__)
16
-
17
- # Load bot token
18
- BOT_TOKEN = "6655373829:AAGduLdLyNx7zUtxH73Sp3Z1vHKS35tV9WU"
19
- if not BOT_TOKEN:
20
- raise ValueError("Bot token is not set in environment variables")
21
-
22
- # Create the bot application
23
- application = Application.builder().token(BOT_TOKEN).build()
24
-
25
- # Define the /start command handler
26
- async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
27
- await update.message.reply_text("Hello! This bot is running on Hugging Face Spaces 🚀")
28
-
29
- # Add command handlers
30
- application.add_handler(CommandHandler("start", start))
31
-
32
- # Function to run polling in a thread-safe way
33
- def run_polling():
34
- loop = asyncio.new_event_loop() # Create a new event loop
35
- asyncio.set_event_loop(loop) # Set it for the current thread
36
- loop.run_until_complete(application.run_polling(close_loop=True)) # Run polling in the loop
37
-
38
- # Streamlit UI
39
- st.title("Telegram Bot on Streamlit")
40
- st.write("This bot is running using Streamlit and Python-Telegram-Bot.")
41
-
42
- # Button to start the bot
43
- if st.button("Start Bot"):
44
- thread = threading.Thread(target=run_polling, daemon=True) # Run polling in a background thread
45
- thread.start()
46
- st.write("Bot started successfully and is now polling Telegram!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
 
 
 
2
  import logging
 
3
  import threading
4
+ import time
5
+ import datetime
6
+ import traceback
7
+ import fractions
8
+ import requests
9
+
10
  from fastapi import FastAPI, Request
11
+ import av
12
 
13
  app = FastAPI()
14
+
15
+ # -------------------------------------------------------------------
16
+ # Configuration & Global Variables
17
+ # -------------------------------------------------------------------
18
+ TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
19
+ TELEGRAM_API_URL = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}"
20
+
21
+ # Conversation state
22
+ user_inputs = {}
23
+ # The conversation fields will depend on the mode.
24
+ # Simple mode (default): Only "input_url" and "output_url" are required.
25
+ # Advanced mode (if user sends /setting): Additional fields are required.
26
+ conversation_fields = []
27
+ current_step = None
28
+ advanced_mode = False
29
+
30
+ # Default settings for advanced fields
31
+ default_settings = {
32
+ "quality_settings": "medium",
33
+ "video_codec": "libx264",
34
+ "audio_codec": "aac",
35
+ "output_url": "rtmp://a.rtmp.youtube.com/live2"
36
+ }
37
+
38
+ # Streaming state & statistics
39
+ streaming_state = "idle" # "idle", "streaming", "paused", "stopped"
40
+ stream_chat_id = None # Chat ID for periodic updates
41
+ stream_start_time = None
42
+ frames_encoded = 0
43
+ bytes_sent = 0
44
+
45
+ # Stream resource objects
46
+ video_stream = None
47
+ audio_stream_in = None
48
+ output_stream = None
49
+
50
+ # Thread references
51
+ stream_thread = None
52
+ live_log_thread = None
53
+
54
+ # Live logging globals
55
+ live_log_lines = [] # Rolling list (max 50 lines)
56
+ live_log_message_id = None
57
+
58
+ # -------------------------------------------------------------------
59
+ # Enhanced Logging Setup
60
+ # -------------------------------------------------------------------
61
+ logging.basicConfig(
62
+ level=logging.DEBUG,
63
+ format="%(asctime)s [%(levelname)s] %(message)s",
64
+ datefmt="%Y-%m-%d %H:%M:%S",
65
+ )
66
+ logger = logging.getLogger()
67
+
68
+ def append_live_log(line: str):
69
+ global live_log_lines
70
+ live_log_lines.append(line)
71
+ if len(live_log_lines) > 50:
72
+ live_log_lines.pop(0)
73
+
74
+ class ListHandler(logging.Handler):
75
+ def emit(self, record):
76
+ log_entry = self.format(record)
77
+ append_live_log(log_entry)
78
+
79
+ list_handler = ListHandler()
80
+ list_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s", "%Y-%m-%d %H:%M:%S"))
81
+ logger.addHandler(list_handler)
82
+
83
+ # -------------------------------------------------------------------
84
+ # Utility Functions & UI Helpers
85
+ # -------------------------------------------------------------------
86
+ def create_markdown_message(text: str):
87
+ return {"parse_mode": "Markdown", "text": text}
88
+
89
+ def get_inline_keyboard_for_stream():
90
+ # Inline keyboard for streaming controls after stream has started
91
+ keyboard = {
92
+ "inline_keyboard": [
93
+ [
94
+ {"text": "⏸ Pause", "callback_data": "pause"},
95
+ {"text": "▶️ Resume", "callback_data": "resume"},
96
+ {"text": "⏹ Abort", "callback_data": "abort"}
97
+ ],
98
+ [
99
+ {"text": "📊 Status", "callback_data": "status"}
100
+ ]
101
+ ]
102
+ }
103
+ return keyboard
104
+
105
+ def get_inline_keyboard_for_start():
106
+ # Inline keyboard with a start button for when conversation is complete.
107
+ keyboard = {
108
+ "inline_keyboard": [
109
+ [
110
+ {"text": "🚀 Start Streaming", "callback_data": "start_stream"}
111
+ ]
112
+ ]
113
+ }
114
+ return keyboard
115
+
116
+ def help_text():
117
+ return (
118
+ "*Stream Bot Help*\n\n"
119
+ "*/start* - Begin setup for streaming (simple mode: only Input & Output URL)\n"
120
+ "*/setting* - Enter advanced settings (Input URL, Quality Settings, Video Codec, Audio Codec, Output URL)\n"
121
+ "*/help* - Display this help text\n\n"
122
+ "After inputs are collected, press the inline *Start Streaming* button.\n\n"
123
+ "While streaming, you can use inline buttons or commands:\n"
124
+ "*/pause* - Pause the stream\n"
125
+ "*/resume* - Resume a paused stream\n"
126
+ "*/abort* - Abort the stream\n"
127
+ "*/status* - Get current stream statistics"
128
+ )
129
+
130
+ def send_guide_message(chat_id, message):
131
+ logging.info(f"Sending message to chat {chat_id}: {message}")
132
+ return {
133
+ "method": "sendMessage",
134
+ "chat_id": chat_id,
135
+ "text": message,
136
+ "parse_mode": "Markdown"
137
+ }
138
+
139
+ def reset_statistics():
140
+ global stream_start_time, frames_encoded, bytes_sent
141
+ stream_start_time = datetime.datetime.now()
142
+ frames_encoded = 0
143
+ bytes_sent = 0
144
+
145
+ def get_uptime():
146
+ if stream_start_time:
147
+ uptime = datetime.datetime.now() - stream_start_time
148
+ return str(uptime).split('.')[0]
149
+ return "0"
150
+
151
+ def validate_inputs():
152
+ # Ensure all fields in conversation_fields have been provided
153
+ missing = [field for field in conversation_fields if field not in user_inputs or not user_inputs[field]]
154
+ if missing:
155
+ return False, f"Missing fields: {', '.join(missing)}"
156
+ return True, ""
157
+
158
+ # -------------------------------------------------------------------
159
+ # Live Logging Updater (Background Thread)
160
+ # -------------------------------------------------------------------
161
+ def live_log_updater(chat_id):
162
+ global live_log_message_id, streaming_state
163
+ try:
164
+ # Send initial live log message
165
+ payload = {
166
+ "chat_id": chat_id,
167
+ "text": "*Live Logs:*\n_(Initializing...)_",
168
+ "parse_mode": "Markdown"
169
+ }
170
+ resp = requests.post(f"{TELEGRAM_API_URL}/sendMessage", json=payload)
171
+ if resp.ok:
172
+ live_log_message_id = resp.json()["result"]["message_id"]
173
+ logging.info(f"Live log message sent with id {live_log_message_id}")
174
+ else:
175
+ logging.error("Failed to send live log message.")
176
+ return
177
+
178
+ # Update live log every 15 seconds until streaming stops
179
+ while streaming_state in ["streaming", "paused"]:
180
+ log_text = "*Live Logs:*\n\n" + "\n".join(live_log_lines[-15:]) # show last 15 lines
181
+ edit_payload = {
182
+ "chat_id": chat_id,
183
+ "message_id": live_log_message_id,
184
+ "text": log_text,
185
+ "parse_mode": "Markdown"
186
+ }
187
+ requests.post(f"{TELEGRAM_API_URL}/editMessageText", json=edit_payload)
188
+ time.sleep(15)
189
+ except Exception as e:
190
+ logging.error(f"Error in live log updater: {e}")
191
+
192
+ # -------------------------------------------------------------------
193
+ # Conversation Handlers
194
+ # -------------------------------------------------------------------
195
+ def handle_start(chat_id):
196
+ global current_step, user_inputs, conversation_fields, advanced_mode
197
+ # By default, simple mode (unless advanced_mode was set via /setting)
198
+ user_inputs = {}
199
+ if not advanced_mode:
200
+ conversation_fields = ["input_url", "output_url"]
201
+ else:
202
+ conversation_fields = ["input_url", "quality_settings", "video_codec", "audio_codec", "output_url"]
203
+ current_step = conversation_fields[0]
204
+ text = ("👋 *Welcome to the Stream Bot!*\n\n"
205
+ "Let's set up your stream.\n"
206
+ f"Please enter the *{current_step.replace('_', ' ')}*"
207
+ f"{' (no default)' if current_step not in default_settings else f' _(default: {default_settings[current_step]})_'}:")
208
+ logging.info(f"/start command from chat {chat_id} (advanced_mode={advanced_mode})")
209
+ return {
210
+ "method": "sendMessage",
211
+ "chat_id": chat_id,
212
+ "text": text,
213
+ "parse_mode": "Markdown"
214
+ }
215
+
216
+ def handle_setting(chat_id):
217
+ global advanced_mode, conversation_fields, current_step, user_inputs
218
+ advanced_mode = True
219
+ conversation_fields = ["input_url", "quality_settings", "video_codec", "audio_codec", "output_url"]
220
+ user_inputs = {}
221
+ current_step = conversation_fields[0]
222
+ text = ("⚙️ *Advanced Mode Activated!*\n\n"
223
+ "Please enter the *input url*:")
224
+ logging.info(f"/setting command from chat {chat_id} - advanced mode enabled")
225
+ return {
226
+ "method": "sendMessage",
227
+ "chat_id": chat_id,
228
+ "text": text,
229
+ "parse_mode": "Markdown"
230
+ }
231
+
232
+ def handle_help(chat_id):
233
+ logging.info(f"/help command from chat {chat_id}")
234
+ return {
235
+ "method": "sendMessage",
236
+ "chat_id": chat_id,
237
+ "text": help_text(),
238
+ "parse_mode": "Markdown"
239
+ }
240
+
241
+ def handle_conversation(chat_id, text):
242
+ global current_step, user_inputs, conversation_fields
243
+ if current_step:
244
+ # If the response is empty and a default exists, use the default.
245
+ if text.strip() == "" and current_step in default_settings:
246
+ user_inputs[current_step] = default_settings[current_step]
247
+ logging.info(f"Using default for {current_step}: {default_settings[current_step]}")
248
+ else:
249
+ user_inputs[current_step] = text.strip()
250
+ logging.info(f"Received {current_step}: {text.strip()}")
251
+
252
+ idx = conversation_fields.index(current_step)
253
+ if idx < len(conversation_fields) - 1:
254
+ current_step = conversation_fields[idx + 1]
255
+ prompt = f"Please enter the *{current_step.replace('_', ' ')}*"
256
+ if current_step in default_settings:
257
+ prompt += f" _(default: {default_settings[current_step]})_"
258
+ return send_guide_message(chat_id, prompt)
259
+ else:
260
+ # All inputs have been collected.
261
+ current_step = None
262
+ valid, msg = validate_inputs()
263
+ if not valid:
264
+ return send_guide_message(chat_id, f"Validation error: {msg}")
265
+ # In simple mode, fill in advanced fields with defaults.
266
+ if not advanced_mode:
267
+ user_inputs.setdefault("quality_settings", default_settings["quality_settings"])
268
+ user_inputs.setdefault("video_codec", default_settings["video_codec"])
269
+ user_inputs.setdefault("audio_codec", default_settings["audio_codec"])
270
+ # Instead of asking user to type "start", send an inline button.
271
+ return {
272
+ "method": "sendMessage",
273
+ "chat_id": chat_id,
274
+ "text": "All inputs received. Press *🚀 Start Streaming* to begin.",
275
+ "reply_markup": get_inline_keyboard_for_start(),
276
+ "parse_mode": "Markdown"
277
+ }
278
+ else:
279
+ return send_guide_message(chat_id, "Unrecognized input. Type /help for available commands.")
280
+
281
+ # -------------------------------------------------------------------
282
+ # Background Streaming Functions
283
+ # -------------------------------------------------------------------
284
+ def stream_to_youtube(input_url, quality_settings, video_codec, audio_codec, output_url, chat_id):
285
+ global video_stream, audio_stream_in, output_stream, streaming_state, frames_encoded, bytes_sent
286
+ logging.info("Initiating streaming to YouTube")
287
+ try:
288
+ streaming_state = "streaming"
289
+ reset_statistics()
290
+
291
+ input_stream = av.open(input_url)
292
+ output_stream = av.open(output_url, mode='w', format='flv')
293
+
294
+ # Configure video stream
295
+ video_stream = output_stream.add_stream(video_codec, rate=30)
296
+ video_stream.width = input_stream.streams.video[0].width
297
+ video_stream.height = input_stream.streams.video[0].height
298
+ video_stream.pix_fmt = input_stream.streams.video[0].format.name
299
+ video_stream.codec_context.options.update({'g': '30'})
300
+
301
+ if quality_settings.lower() == "high":
302
+ video_stream.bit_rate = 3000000
303
+ video_stream.bit_rate_tolerance = 1000000
304
+ elif quality_settings.lower() == "medium":
305
+ video_stream.bit_rate = 1500000
306
+ video_stream.bit_rate_tolerance = 500000
307
+ elif quality_settings.lower() == "low":
308
+ video_stream.bit_rate = 800000
309
+ video_stream.bit_rate_tolerance = 200000
310
+
311
+ # Configure audio stream
312
+ audio_stream_in = input_stream.streams.audio[0]
313
+ out_audio_stream = output_stream.add_stream(audio_codec, rate=audio_stream_in.rate)
314
+ out_audio_stream.layout = "stereo"
315
+
316
+ video_stream.codec_context.time_base = fractions.Fraction(1, video_stream.rate)
317
+
318
+ logging.info("Streaming started successfully.")
319
+
320
+ # Stream loop: process packets until state changes
321
+ while streaming_state in ["streaming", "paused"]:
322
+ for packet in input_stream.demux():
323
+ if streaming_state == "stopped":
324
+ break
325
+ if packet.stream == input_stream.streams.video[0]:
326
+ for frame in packet.decode():
327
+ if streaming_state == "paused":
328
+ time.sleep(0.5)
329
+ continue
330
+ for out_packet in video_stream.encode(frame):
331
+ output_stream.mux(out_packet)
332
+ frames_encoded += 1
333
+ if hasattr(out_packet, "size"):
334
+ bytes_sent += out_packet.size
335
+ elif packet.stream == audio_stream_in:
336
+ for frame in packet.decode():
337
+ if streaming_state == "paused":
338
+ time.sleep(0.5)
339
+ continue
340
+ for out_packet in out_audio_stream.encode(frame):
341
+ output_stream.mux(out_packet)
342
+ if hasattr(out_packet, "size"):
343
+ bytes_sent += out_packet.size
344
+
345
+ # Flush remaining packets
346
+ for out_packet in video_stream.encode():
347
+ output_stream.mux(out_packet)
348
+ for out_packet in out_audio_stream.encode():
349
+ output_stream.mux(out_packet)
350
+
351
+ if streaming_state == "paused":
352
+ time.sleep(1)
353
+
354
+ # Clean up resources
355
+ try:
356
+ video_stream.close()
357
+ out_audio_stream.close()
358
+ output_stream.close()
359
+ input_stream.close()
360
+ except Exception as cleanup_error:
361
+ logging.error(f"Error during cleanup: {cleanup_error}")
362
+
363
+ logging.info("Streaming complete, resources cleaned up.")
364
+ streaming_state = "idle"
365
+ except Exception as e:
366
+ error_message = f"An error occurred during streaming: {str(e)}"
367
+ logging.error(error_message)
368
+ logging.error(traceback.format_exc())
369
+ streaming_state = "idle"
370
+
371
+ def start_streaming(chat_id):
372
+ global stream_thread, live_log_thread, stream_chat_id
373
+ valid, msg = validate_inputs()
374
+ if not valid:
375
+ return send_guide_message(chat_id, f"Validation error: {msg}")
376
+
377
+ stream_chat_id = chat_id
378
+ try:
379
+ # Start the background streaming thread
380
+ stream_thread = threading.Thread(
381
+ target=stream_to_youtube,
382
+ args=(
383
+ user_inputs["input_url"],
384
+ user_inputs["quality_settings"],
385
+ user_inputs["video_codec"],
386
+ user_inputs["audio_codec"],
387
+ user_inputs["output_url"],
388
+ chat_id,
389
+ )
390
+ )
391
+ stream_thread.daemon = True
392
+ stream_thread.start()
393
+ logging.info("Streaming thread started.")
394
+
395
+ # Start the live log updater thread
396
+ live_log_thread = threading.Thread(target=live_log_updater, args=(chat_id,))
397
+ live_log_thread.daemon = True
398
+ live_log_thread.start()
399
+ logging.info("Live log updater started.")
400
+
401
+ return {
402
+ "method": "sendMessage",
403
+ "chat_id": chat_id,
404
+ "text": "🚀 *Streaming initiated!* Use the inline keyboard to control the stream.",
405
+ "reply_markup": get_inline_keyboard_for_stream(),
406
+ "parse_mode": "Markdown"
407
+ }
408
+ except Exception as e:
409
+ error_message = f"Failed to start streaming: {str(e)}"
410
+ logging.error(error_message)
411
+ return send_guide_message(chat_id, error_message)
412
+
413
+ # -------------------------------------------------------------------
414
+ # Stream Control Handlers
415
+ # -------------------------------------------------------------------
416
+ def pause_stream(chat_id):
417
+ global streaming_state
418
+ if streaming_state == "streaming":
419
+ streaming_state = "paused"
420
+ logging.info("Streaming paused.")
421
+ return {
422
+ "method": "sendMessage",
423
+ "chat_id": chat_id,
424
+ "text": "⏸ *Streaming paused.*",
425
+ "parse_mode": "Markdown"
426
+ }
427
+ return send_guide_message(chat_id, "Streaming is not active.")
428
+
429
+ def resume_stream(chat_id):
430
+ global streaming_state
431
+ if streaming_state == "paused":
432
+ streaming_state = "streaming"
433
+ logging.info("Streaming resumed.")
434
+ return {
435
+ "method": "sendMessage",
436
+ "chat_id": chat_id,
437
+ "text": "▶️ *Streaming resumed.*",
438
+ "parse_mode": "Markdown"
439
+ }
440
+ return send_guide_message(chat_id, "Streaming is not paused.")
441
+
442
+ def abort_stream(chat_id):
443
+ global streaming_state
444
+ if streaming_state in ["streaming", "paused"]:
445
+ streaming_state = "stopped"
446
+ logging.info("Streaming aborted by user.")
447
+ return {
448
+ "method": "sendMessage",
449
+ "chat_id": chat_id,
450
+ "text": "⏹ *Streaming aborted.*",
451
+ "parse_mode": "Markdown"
452
+ }
453
+ return send_guide_message(chat_id, "No active streaming to abort.")
454
+
455
+ def stream_status(chat_id):
456
+ stats = (
457
+ f"*Stream Status:*\n\n"
458
+ f"• **State:** {streaming_state}\n"
459
+ f"• **Uptime:** {get_uptime()}\n"
460
+ f"• **Frames Encoded:** {frames_encoded}\n"
461
+ f"• **Bytes Sent:** {bytes_sent}\n"
462
+ )
463
+ return {
464
+ "method": "sendMessage",
465
+ "chat_id": chat_id,
466
+ "text": stats,
467
+ "parse_mode": "Markdown"
468
+ }
469
+
470
+ # -------------------------------------------------------------------
471
+ # FastAPI Webhook Endpoint for Telegram Updates
472
+ # -------------------------------------------------------------------
473
+ @app.post("/webhook")
474
+ async def telegram_webhook(request: Request):
475
+ update = await request.json()
476
+ logging.debug(f"Received update: {update}")
477
+
478
+ # Process messages from users
479
+ if "message" in update:
480
+ chat_id = update["message"]["chat"]["id"]
481
+ text = update["message"].get("text", "").strip()
482
+
483
+ if text.startswith("/setting"):
484
+ return handle_setting(chat_id)
485
+ elif text.startswith("/start"):
486
+ return handle_start(chat_id)
487
+ elif text.startswith("/help"):
488
+ return handle_help(chat_id)
489
+ elif text.startswith("/pause"):
490
+ return pause_stream(chat_id)
491
+ elif text.startswith("/resume"):
492
+ return resume_stream(chat_id)
493
+ elif text.startswith("/abort"):
494
+ return abort_stream(chat_id)
495
+ elif text.startswith("/status"):
496
+ return stream_status(chat_id)
497
+ else:
498
+ # Process conversation setup inputs
499
+ return handle_conversation(chat_id, text)
500
+
501
+ # Process inline keyboard callback queries
502
+ elif "callback_query" in update:
503
+ callback_data = update["callback_query"]["data"]
504
+ chat_id = update["callback_query"]["message"]["chat"]["id"]
505
+ message_id = update["callback_query"]["message"]["message_id"]
506
+
507
+ if callback_data == "pause":
508
+ response = pause_stream(chat_id)
509
+ elif callback_data == "resume":
510
+ response = resume_stream(chat_id)
511
+ elif callback_data == "abort":
512
+ response = abort_stream(chat_id)
513
+ elif callback_data == "status":
514
+ response = stream_status(chat_id)
515
+ elif callback_data == "start_stream":
516
+ response = start_streaming(chat_id)
517
+ else:
518
+ response = send_guide_message(chat_id, "❓ Unknown callback command.")
519
+
520
+ # Edit the original message with updated information
521
+ response["method"] = "editMessageText"
522
+ response["message_id"] = message_id
523
+ return response
524
+
525
+ return {"status": "ok"}