Enutrof commited on
Commit
f8f6e4d
·
1 Parent(s): bd5243c

Separated download functionality and added post init.

Browse files
Files changed (1) hide show
  1. bot.py +53 -21
bot.py CHANGED
@@ -7,6 +7,7 @@ import os
7
  import asyncio
8
  import yt_dlp
9
  from telegram import Update, InputFile
 
10
  from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
11
  from telegram.constants import ParseMode
12
  from dotenv import load_dotenv
@@ -51,9 +52,9 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> N
51
  user = update.effective_user
52
  welcome_message = (
53
  f"👋 Hello {user.mention_html()}!\n\n"
54
- "I'm your friendly video downloading bot. Send me a link to a video from "
55
- "sites like YouTube, Vimeo, Twitter, etc., and I'll try my best to download "
56
- "and send it to you.\n\n"
57
  "Keep in mind Telegram has a file size limit of about 50MB for bots, "
58
  "so I'll try to get a version under that size.\n\n"
59
  "Type /help for more information."
@@ -64,7 +65,8 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
64
  """Sends a help message when the /help command is issued."""
65
  help_text = (
66
  "ℹ️ **How to use me:**\n"
67
- "1. Simply send me a direct URL to a video you want to download.\n"
 
68
  "2. I will process the link, download the video, and send it back to you.\n\n"
69
  "**Supported Sites:**\n"
70
  "I use `yt-dlp`, which supports hundreds of websites. Common ones include YouTube, "
@@ -77,11 +79,7 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
77
  )
78
  await update.message.reply_text(help_text, parse_mode=ParseMode.MARKDOWN)
79
 
80
-
81
- # --- Main Video Processing Logic ---
82
- async def handle_video_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
83
- """Handles messages containing video URLs."""
84
- url = update.message.text
85
  chat_id = update.effective_chat.id
86
 
87
  if not url.startswith(('http://', 'https://')):
@@ -102,7 +100,7 @@ async def handle_video_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -
102
  ydl_opts = {
103
  'format': f'bestvideo[ext=mp4][filesize<{MAX_FILE_SIZE_MB}M]+bestaudio[ext=m4a]/best[ext=mp4][filesize<{MAX_FILE_SIZE_MB}M]/best[filesize<{MAX_FILE_SIZE_MB}M]',
104
  'outtmpl': os.path.join(DOWNLOAD_PATH, '%(title).200B.%(ext)s'), # Limit title length to avoid overly long filenames
105
- # 'noplaylist': True, # Download only single video if playlist URL is given
106
  # 'quiet': True, # Suppress yt-dlp console output
107
  'merge_output_format': 'mp4', # Ensure output is mp4 if merging is needed
108
  # # 'max_filesize': MAX_FILE_SIZE_MB * 1024 * 1024, # Alternative way to specify max filesize
@@ -111,7 +109,7 @@ async def handle_video_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -
111
  # 'preferedformat': 'mp4',
112
  # }],
113
  # 'logger': logger, # Send yt-dlp logs to our logger
114
- # 'progress_hooks': [lambda d: download_progress_hook(d, update, context, processing_message.message_id)],
115
  }
116
 
117
  # Use asyncio.to_thread to run blocking yt-dlp code in a separate thread
@@ -179,7 +177,29 @@ async def handle_video_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -
179
  logger.error(f"yt-dlp generic error for URL {url}: {e}")
180
  await processing_message.edit_text(f"❌ An error occurred during video processing: {str(e)}")
181
  return
 
 
 
 
 
 
 
 
 
 
182
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  if downloaded_file_path and os.path.exists(downloaded_file_path):
184
  file_size = os.path.getsize(downloaded_file_path)
185
 
@@ -228,19 +248,20 @@ async def handle_video_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -
228
 
229
 
230
  except Exception as e:
231
- logger.error(f"An unexpected error occurred for URL {url}: {e}", exc_info=True)
232
  await processing_message.edit_text("❌ An unexpected error occurred. Please try again later.")
233
  finally:
234
- # # Clean up the downloaded file
235
- # if downloaded_file_path and os.path.exists(downloaded_file_path):
236
- # try:
237
- # os.remove(downloaded_file_path)
238
- # logger.info(f"Cleaned up downloaded file: {downloaded_file_path}")
239
- # except OSError as e:
240
- # logger.error(f"Error deleting file {downloaded_file_path}: {e}")
241
  # Clear any stored filename from chat_data
242
  context.chat_data.pop(f'download_filename_{chat_id}', None)
243
  context.chat_data.pop(f'download_error_{chat_id}', None)
 
244
 
245
 
246
  # --- yt-dlp Progress Hook ---
@@ -314,6 +335,15 @@ async def download_progress_hook(d, update: Update, context: ContextTypes.DEFAUL
314
  except Exception:
315
  pass # Ignore if editing fails
316
 
 
 
 
 
 
 
 
 
 
317
 
318
  # --- Main Bot Execution ---
319
  def main() -> None:
@@ -325,14 +355,16 @@ def main() -> None:
325
  ensure_download_path_exists()
326
 
327
  # Create the Application and pass it your bot's token.
328
- application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
329
 
330
  # Add command handlers
331
  application.add_handler(CommandHandler("start", start_command))
332
  application.add_handler(CommandHandler("help", help_command))
 
 
333
 
334
  # Add message handler for video URLs (non-command text messages)
335
- application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_video_url))
336
 
337
  # Run the bot until the user presses Ctrl-C
338
  logger.info("Bot starting...")
 
7
  import asyncio
8
  import yt_dlp
9
  from telegram import Update, InputFile
10
+ from telegram import BotCommand
11
  from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
12
  from telegram.constants import ParseMode
13
  from dotenv import load_dotenv
 
52
  user = update.effective_user
53
  welcome_message = (
54
  f"👋 Hello {user.mention_html()}!\n\n"
55
+ "I'm your friendly video downloading bot.\n"
56
+ "Use the `/download <video_url>` command to fetch a video.\n\n"
57
+ "Example: `/download https://www.youtube.com/watch?v=your_video_id`\n\n" # Corrected example URL
58
  "Keep in mind Telegram has a file size limit of about 50MB for bots, "
59
  "so I'll try to get a version under that size.\n\n"
60
  "Type /help for more information."
 
65
  """Sends a help message when the /help command is issued."""
66
  help_text = (
67
  "ℹ️ **How to use me:**\n"
68
+ "1. Use the `/download` command followed by a direct URL to the video you want to download.\n"
69
+ " Example: `/download https://www.example.com/video.mp4`\n"
70
  "2. I will process the link, download the video, and send it back to you.\n\n"
71
  "**Supported Sites:**\n"
72
  "I use `yt-dlp`, which supports hundreds of websites. Common ones include YouTube, "
 
79
  )
80
  await update.message.reply_text(help_text, parse_mode=ParseMode.MARKDOWN)
81
 
82
+ async def downloader(update: Update, context: ContextTypes.DEFAULT_TYPE, url: str) -> tuple:
 
 
 
 
83
  chat_id = update.effective_chat.id
84
 
85
  if not url.startswith(('http://', 'https://')):
 
100
  ydl_opts = {
101
  'format': f'bestvideo[ext=mp4][filesize<{MAX_FILE_SIZE_MB}M]+bestaudio[ext=m4a]/best[ext=mp4][filesize<{MAX_FILE_SIZE_MB}M]/best[filesize<{MAX_FILE_SIZE_MB}M]',
102
  'outtmpl': os.path.join(DOWNLOAD_PATH, '%(title).200B.%(ext)s'), # Limit title length to avoid overly long filenames
103
+ 'noplaylist': True, # Download only single video if playlist URL is given
104
  # 'quiet': True, # Suppress yt-dlp console output
105
  'merge_output_format': 'mp4', # Ensure output is mp4 if merging is needed
106
  # # 'max_filesize': MAX_FILE_SIZE_MB * 1024 * 1024, # Alternative way to specify max filesize
 
109
  # 'preferedformat': 'mp4',
110
  # }],
111
  # 'logger': logger, # Send yt-dlp logs to our logger
112
+ 'progress_hooks': [lambda d: download_progress_hook(d, update, context, processing_message.message_id)],
113
  }
114
 
115
  # Use asyncio.to_thread to run blocking yt-dlp code in a separate thread
 
177
  logger.error(f"yt-dlp generic error for URL {url}: {e}")
178
  await processing_message.edit_text(f"❌ An error occurred during video processing: {str(e)}")
179
  return
180
+ except:
181
+ logger.error(f"An overarching unexpected error occurred while downloading URL {url}: {e}", exc_info=True)
182
+ await processing_message.edit_text("❌ An unexpected error occurred. Please try again later.")
183
+ return downloaded_file_path, processing_message
184
+
185
+
186
+ # --- Main Video Processing Logic ---
187
+ async def download_command_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
188
+ """Handles messages containing video URLs."""
189
+ chat_id = update.effective_chat.id
190
 
191
+ if not context.args:
192
+ await update.message.reply_text(
193
+ "⚠️ Please provide a video URL after the /download command.\n"
194
+ "Example: `/download https://www.youtube.com/watch?v=dQw4w9WgXcQ`", # Corrected example URL
195
+ parse_mode=ParseMode.MARKDOWN
196
+ )
197
+ return
198
+
199
+ url = context.args[0] # The first argument after /download
200
+
201
+ try:
202
+ downloaded_file_path, processing_message = await downloader(update, context, url)
203
  if downloaded_file_path and os.path.exists(downloaded_file_path):
204
  file_size = os.path.getsize(downloaded_file_path)
205
 
 
248
 
249
 
250
  except Exception as e:
251
+ logger.error(f"An overarching unexpected error occurred for URL {url}: {e}", exc_info=True)
252
  await processing_message.edit_text("❌ An unexpected error occurred. Please try again later.")
253
  finally:
254
+ # Clean up the downloaded file
255
+ if downloaded_file_path and os.path.exists(downloaded_file_path):
256
+ try:
257
+ os.remove(downloaded_file_path)
258
+ logger.info(f"Cleaned up downloaded file: {downloaded_file_path}")
259
+ except OSError as e:
260
+ logger.error(f"Error deleting file {downloaded_file_path}: {e}")
261
  # Clear any stored filename from chat_data
262
  context.chat_data.pop(f'download_filename_{chat_id}', None)
263
  context.chat_data.pop(f'download_error_{chat_id}', None)
264
+ context.chat_data.pop(f'last_progress_msg_{chat_id}', None)
265
 
266
 
267
  # --- yt-dlp Progress Hook ---
 
335
  except Exception:
336
  pass # Ignore if editing fails
337
 
338
+ async def post_init(application: Application) -> None:
339
+ """Sets the bot's commands after initialization."""
340
+ commands = [
341
+ BotCommand("start", "Starts the bot and shows a welcome message."),
342
+ BotCommand("help", "Shows the help message with instructions."),
343
+ BotCommand("download", "Downloads a video from a given URL (e.g., /download <URL>).")
344
+ ]
345
+ await application.bot.set_my_commands(commands)
346
+ logger.info("Bot commands have been set programmatically.")
347
 
348
  # --- Main Bot Execution ---
349
  def main() -> None:
 
355
  ensure_download_path_exists()
356
 
357
  # Create the Application and pass it your bot's token.
358
+ application = Application.builder().token(TELEGRAM_BOT_TOKEN).post_init(post_init).build()
359
 
360
  # Add command handlers
361
  application.add_handler(CommandHandler("start", start_command))
362
  application.add_handler(CommandHandler("help", help_command))
363
+ application.add_handler(CommandHandler("download", download_command_handler)) # New download handler
364
+
365
 
366
  # Add message handler for video URLs (non-command text messages)
367
+ # application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_video_url))
368
 
369
  # Run the bot until the user presses Ctrl-C
370
  logger.info("Bot starting...")