Baha Joe commited on
Commit
87d591a
·
1 Parent(s): b6abfcb

Refactor bot.py for robustness and better logging

Browse files
Files changed (1) hide show
  1. bot.py +104 -101
bot.py CHANGED
@@ -1,125 +1,128 @@
1
  import os
2
- import shutil
3
- from dotenv import load_dotenv
4
- from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove
5
- from telegram.ext import Application, CommandHandler, MessageHandler, ConversationHandler, ContextTypes, filters
6
- from video_utils import download_video, create_clips, generate_tags
7
 
8
- # Load env variables
9
- load_dotenv()
10
- TOKEN = os.getenv("BOT_TOKEN")
 
 
 
11
 
12
- # States for Conversation
13
- URL, NUM_CLIPS, DURATION, PROCESSING = range(4)
 
 
 
 
 
 
14
 
15
- async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  await update.message.reply_text(
17
- "👋 **Welcome to the AI Clipper Bot!**\n\n"
18
- "I can download YouTube videos, create clips, add captions, and generate tags.\n"
19
- "Send /cancel at any time to stop.\n\n"
20
- "Please send me the **YouTube URL** to get started."
21
  )
22
- return URL
23
 
24
- async def receive_url(update: Update, context: ContextTypes.DEFAULT_TYPE):
25
- url = update.message.text
26
- if "youtube.com" not in url and "youtu.be" not in url:
27
- await update.message.reply_text("❌ That doesn't look like a valid YouTube URL. Please try again.")
28
- return URL
29
 
30
- context.user_data['url'] = url
31
- await update.message.reply_text(" URL received!\n\nHow many clips would you like to generate? (e.g., 3)")
32
- return NUM_CLIPS
33
-
34
- async def receive_num_clips(update: Update, context: ContextTypes.DEFAULT_TYPE):
35
- try:
36
- count = int(update.message.text)
37
- if count > 10:
38
- await update.message.reply_text("⚠️ Let's keep it under 10 clips for performance. Try again.")
39
- return NUM_CLIPS
40
 
41
- context.user_data['num_clips'] = count
42
- await update.message.reply_text("Got it. How long should each clip be (in seconds)? (e.g., 30)")
43
- return DURATION
44
- except ValueError:
45
- await update.message.reply_text("❌ Please enter a valid number.")
46
- return NUM_CLIPS
47
-
48
- async def receive_duration(update: Update, context: ContextTypes.DEFAULT_TYPE):
49
  try:
50
- duration = int(update.message.text)
51
- context.user_data['duration'] = duration
 
 
 
 
 
 
 
 
 
 
 
52
 
53
- await update.message.reply_text(
54
- f"🎬 **Processing Started!**\n"
55
- f"Video: {context.user_data['url']}\n"
56
- f"Clips: {context.user_data['num_clips']}\n"
57
- f"Duration: {duration}s\n\n"
58
- "This will take a few minutes depending on your PC speed. I'll send the clips when ready!"
 
 
59
  )
60
-
61
- # Trigger the processing function asynchronously
62
- # We use create_task so the bot doesn't freeze
63
- context.application.create_task(process_video(update, context))
64
-
65
- return ConversationHandler.END
66
- except ValueError:
67
- await update.message.reply_text("❌ Please enter a valid number for seconds.")
68
- return DURATION
69
 
70
- async def process_video(update: Update, context: ContextTypes.DEFAULT_TYPE):
71
- user_id = update.effective_user.id
72
- data = context.user_data
73
-
74
- try:
75
- # 1. Download
76
- video_path, title, _ = download_video(data['url'])
77
-
78
- # 2. Generate Tags
79
- tags = generate_tags(title)
80
-
81
- # 3. Process Clips
82
- clips = create_clips(video_path, data['num_clips'], data['duration'])
83
-
84
- # 4. Send Clips
85
- for clip_path in clips:
86
- await context.bot.send_video(
87
- chat_id=user_id,
88
- video=open(clip_path, 'rb'),
89
- caption=f"🎥 **{title}**\n\n{tags}",
90
- read_timeout=60
91
  )
92
- # Clean up individual clip after sending
93
- os.remove(clip_path)
94
 
95
- # Cleanup original video
96
- os.remove(video_path)
97
-
98
- await context.bot.send_message(chat_id=user_id, text="✅ All clips sent! Send /start to process another.")
99
-
100
  except Exception as e:
101
- await context.bot.send_message(chat_id=user_id, text=f"An error occurred: {str(e)}")
 
 
 
 
 
 
102
 
103
- async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
104
- await update.message.reply_text("🚫 Operation cancelled. Send /start to begin again.", reply_markup=ReplyKeyboardRemove())
105
- return ConversationHandler.END
106
 
107
- def main():
 
 
 
 
 
 
108
  application = Application.builder().token(TOKEN).build()
109
 
110
- conv_handler = ConversationHandler(
111
- entry_points=[CommandHandler("start", start)],
112
- states={
113
- URL: [MessageHandler(filters.TEXT & ~filters.COMMAND, receive_url)],
114
- NUM_CLIPS: [MessageHandler(filters.TEXT & ~filters.COMMAND, receive_num_clips)],
115
- DURATION: [MessageHandler(filters.TEXT & ~filters.COMMAND, receive_duration)],
116
- },
117
- fallbacks=[CommandHandler("cancel", cancel)],
118
- )
119
 
120
- application.add_handler(conv_handler)
121
- print("Bot is running...")
122
- application.run_polling()
 
123
 
124
  if __name__ == "__main__":
125
  main()
 
1
  import os
2
+ import sys
3
+ import logging
4
+ from telegram import Update
5
+ from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
6
+ from video_utils import process_video_clip
7
 
8
+ # Configure logging
9
+ logging.basicConfig(
10
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
11
+ level=logging.INFO
12
+ )
13
+ logger = logging.getLogger(__name__)
14
 
15
+ # --- Configuration ---
16
+ # ⚠️ WARNING: Hardcoding the token is strongly discouraged for security reasons.
17
+ # Use Hugging Face Secrets instead.
18
+ # If you must hardcode it, replace the line below with the token you provided:
19
+ # TOKEN = "8575159633:AAHt8KYNKLrWKID8FOyZipEcPAxZ_zdjgg4"
20
+ #
21
+ # If you are using Hugging Face Secrets (RECOMMENDED):
22
+ TOKEN = os.getenv("BOT_TOKEN")
23
 
24
+ # Ensure the token is available
25
+ if not TOKEN:
26
+ logger.error("FATAL: BOT_TOKEN is missing. Please set it as a Hugging Face Secret.")
27
+ sys.exit(1)
28
+
29
+ # Ensure necessary directories exist
30
+ DOWNLOAD_DIR = "downloads"
31
+ CLIP_DIR = "clips"
32
+ os.makedirs(DOWNLOAD_DIR, exist_ok=True)
33
+ os.makedirs(CLIP_DIR, exist_ok=True)
34
+
35
+ # --- Command Handlers ---
36
+
37
+ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
38
+ """Sends a welcome message when the /start command is issued."""
39
+ logger.info(f"Received /start command from user {update.effective_user.id}")
40
  await update.message.reply_text(
41
+ '👋 Welcome to the AI Telegram Video Clipper Bot!\n\n'
42
+ 'Send me a YouTube video link and specify the start and end times '
43
+ '(e.g., `https://youtu.be/example 0:30-1:00`).\n\n'
44
+ 'I will generate the clip, transcribe the audio, and send the final video back!'
45
  )
 
46
 
47
+ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
48
+ """Handles messages containing a potential YouTube link."""
49
+ user_message = update.message.text
50
+ user_id = update.effective_user.id
 
51
 
52
+ # 1. Simple check for a link and time format
53
+ if "youtu" not in user_message and "0:" not in user_message and "1:" not in user_message:
54
+ logger.info(f"User {user_id} sent non-link message: {user_message[:20]}...")
55
+ return
 
 
 
 
 
 
56
 
 
 
 
 
 
 
 
 
57
  try:
58
+ # Separate link and time frame
59
+ parts = user_message.split()
60
+ url = parts[0]
61
+ time_range = parts[1] if len(parts) > 1 else None
62
+
63
+ if not time_range:
64
+ await update.message.reply_text(
65
+ "Please specify the clip range (e.g., `1:30-2:00`) after the link."
66
+ )
67
+ return
68
+
69
+ logger.info(f"User {user_id} requested clip: {url} from {time_range}")
70
+ await update.message.reply_text(f"Processing your request for: {time_range}...")
71
 
72
+ # 2. Call the core processing function
73
+ final_clip_path = await process_video_clip(
74
+ url,
75
+ time_range,
76
+ DOWNLOAD_DIR,
77
+ CLIP_DIR,
78
+ user_id,
79
+ logger # Pass logger for better tracking
80
  )
 
 
 
 
 
 
 
 
 
81
 
82
+ if final_clip_path:
83
+ # 3. Send the final video back
84
+ logger.info(f"Sending final clip to user {user_id}: {final_clip_path}")
85
+ await update.message.reply_video(
86
+ video=open(final_clip_path, 'rb'),
87
+ caption=f"✅ Your clip from {time_range} is ready!",
88
+ supports_streaming=True
89
+ )
90
+
91
+ else:
92
+ await update.message.reply_text(
93
+ "❌ Error processing your clip. Please check the URL and time format (e.g., 0:30-1:00) and ensure the video is available."
 
 
 
 
 
 
 
 
 
94
  )
 
 
95
 
 
 
 
 
 
96
  except Exception as e:
97
+ logger.error(f"An unexpected error occurred during processing for user {user_id}: {e}", exc_info=True)
98
+ await update.message.reply_text("An unexpected error occurred. Please check the format and try again.")
99
+
100
+ finally:
101
+ # 4. Clean up files after processing
102
+ # Ensure your video_utils handles cleanup of temporary files (Crucial for free tier)
103
+ pass
104
 
 
 
 
105
 
106
+ # --- Main Application Runner ---
107
+
108
+ def main() -> None:
109
+ """Start the bot."""
110
+ logger.info("Starting Telegram Application...")
111
+
112
+ # 1. Build the application
113
  application = Application.builder().token(TOKEN).build()
114
 
115
+ # 2. Register handlers
116
+ application.add_handler(CommandHandler("start", start_command))
117
+ application.add_handler(MessageHandler(
118
+ filters.TEXT & ~filters.COMMAND,
119
+ handle_message
120
+ ))
 
 
 
121
 
122
+ # 3. Start polling
123
+ logger.info("Bot is ready. Starting polling...")
124
+ application.run_polling(allowed_updates=Update.ALL_TYPES)
125
+ logger.info("Bot polling stopped.")
126
 
127
  if __name__ == "__main__":
128
  main()