"""Telegram bot for managing sticker packs.""" import logging from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton from telegram.ext import ( Application, CommandHandler, MessageHandler, CallbackQueryHandler, ConversationHandler, ContextTypes, filters, ) from config import BOT_TOKEN, OWNER_ID, PREMIUM_CHECK_INTERVAL, REMOVE_BG_API_KEY, logger from database import db from sticker_manager import ( check_owner, check_premium_status, get_main_menu_keyboard, get_set_action_keyboard, get_user_sets_keyboard, create_sticker_set, add_sticker_to_set, add_video_sticker_to_set, delete_sticker_from_set, delete_sticker_set, rename_sticker_set, change_sticker_emoji, schedule_premium_check, add_existing_sticker_set, ) from gif_processor import ( get_gif_info, validate_gif_duration, convert_gif_to_webm, ) from background_remover import ( remove_background_local, remove_background_api, should_show_api_limit_warning, validate_image_for_bg_removal, MAX_API_CALLS_PER_MONTH, ) # Conversation states (MENU, CREATE_SET_NAME, CREATE_SET_TITLE, CREATE_SET_EMOJI, CREATE_SET_STICKER, SELECT_SET, SET_ACTION, ADD_STICKER, ADD_STICKER_EMOJI, ADD_ANIMATION_EMOJI, DELETE_STICKER, CHANGE_EMOJI, RENAME_SET, DELETE_CONFIRM, GIF_DURATION_CONFIRM, GIF_CONVERTING, REMOVE_BG_CHOICE, REMOVE_BG_API_ERROR, ADD_EXISTING_SET_NAME) = range(19) # Data keys CURRENT_SET = "current_set" SET_NAME = "set_name" SET_TITLE = "set_title" STICKER_TO_CHANGE = "sticker_to_change" PENDING_ANIMATION = "pending_animation" async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Send welcome message and show main menu.""" if not await check_owner(update): await update.message.reply_text("⛔️ Dieser Bot ist nur für den Besitzer verfügbar.") return ConversationHandler.END # Check premium status on start is_premium = await check_premium_status(update, context) premium_text = "💎 Premium aktiv" if is_premium else "👤 Standard" welcome_text = ( f"🎨 **Willkommen beim Sticker Bot Manager!**\n\n" f"Status: {premium_text}\n\n" "Mit diesem Bot kannst du deine Telegram Stickerpakete verwalten:\n\n" "✅ **Neue Pakete erstellen**\n" "✅ **Sticker hinzufügen** (Bilder & GIFs)\n" "✅ **Sticker löschen**\n" "✅ **Emoji ändern**\n" "✅ **Pakete umbenennen**\n" "✅ **Pakete löschen**\n\n" "🎨 **Extras:**\n" "• Automatische Bildoptimierung\n" "• GIF → animierte Video-Sticker\n" "• Hintergrundentfernung (optional)\n\n" "Nutze /hilfe für Details.\n\n" "Wähle eine Aktion:" ) await update.message.reply_text( welcome_text, reply_markup=get_main_menu_keyboard(), parse_mode="Markdown" ) return MENU async def menu_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Handle menu callbacks.""" query = update.callback_query await query.answer() data = query.data if data == "create_set": await query.edit_message_text( "📝 **Neues Stickerpaket erstellen**\n\n" "Schritt 1/4: Gib einen kurzen Namen ein\n" "(nur Kleinbuchstaben, Unterstriche, Zahlen):" ) return CREATE_SET_NAME elif data == "list_sets": keyboard, text = await get_user_sets_keyboard(update.effective_user.id) await query.edit_message_text( text, reply_markup=keyboard, parse_mode="Markdown" ) return SELECT_SET elif data == "help": await query.edit_message_text( "❓ **Hilfe**\n\n" "**Funktionen:**\n" "• Neues Stickerpaket erstellen\n" "• Sticker zu Paket hinzufügen (Bilder & GIFs)\n" "• Sticker aus Paket löschen\n" "• Emoji eines Stickers ändern\n" "• Paket umbenennen\n" "• Paket löschen\n\n" "**Tipps:**\n" "• Bilder: 512x512 PNG/WebP (automatisch konvertiert)\n" "• GIFs: Werden zu animierten Video-Stickern\n" "• Max. 3 Sekunden für Animationen\n" "• Nutze /start um zum Hauptmenü zurückzukehren", reply_markup=InlineKeyboardMarkup([ [InlineKeyboardButton("⬅️ Zurück", callback_data="back_to_menu")] ]) ) return MENU elif data == "add_existing": await query.edit_message_text( "📥 **Existierendes Stickerpaket hinzufügen**\n\n" "Gib den Namen des Sticker-Pakets ein:\n\n" "**Formate:**\n" "• Kurzname: `meine_sticker` (wird automatisch ergänzt)\n" "• Vollständig: `meine_sticker_by_botname`\n\n" "Du findest den Namen im Sticker-Paket unter 'Info'." ) return ADD_EXISTING_SET_NAME elif data == "back_to_menu": is_premium = await check_premium_status(update, context) premium_text = "💎 Premium aktiv" if is_premium else "👤 Standard" await query.edit_message_text( f"🎨 **Sticker Bot Manager**\n" f"Status: {premium_text}\n\n" "Wähle eine Aktion:", reply_markup=get_main_menu_keyboard(), parse_mode="Markdown" ) return MENU return MENU async def create_set_name(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Get sticker set name from user.""" name = update.message.text.strip().lower().replace(" ", "_") # Validate name if not all(c.isalnum() or c == "_" for c in name): await update.message.reply_text( "⚠️ Ungültiger Name. Nur Kleinbuchstaben, Zahlen und Unterstriche erlaubt.\n" "Versuche es erneut:" ) return CREATE_SET_NAME if len(name) > 60: await update.message.reply_text( "⚠️ Name zu lang (max. 60 Zeichen). Versuche es erneut:" ) return CREATE_SET_NAME context.user_data[SET_NAME] = name await update.message.reply_text( "📝 **Schritt 2/4**\n\n" "Gib nun einen Titel für das Stickerpaket ein:" ) return CREATE_SET_TITLE async def create_set_title(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Get sticker set title from user.""" title = update.message.text.strip() if len(title) > 64: await update.message.reply_text( "⚠️ Titel zu lang (max. 64 Zeichen). Versuche es erneut:" ) return CREATE_SET_TITLE context.user_data[SET_TITLE] = title await update.message.reply_text( "🏷 **Schritt 3/4**\n\n" "Gib ein Emoji für das erste Sticker ein (z.B. 🤖):" ) return CREATE_SET_EMOJI async def create_set_emoji(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Get emoji for first sticker.""" emoji = update.message.text.strip() if len(emoji) > 2: await update.message.reply_text( "⚠️ Bitte gib nur ein Emoji ein. Versuche es erneut:" ) return CREATE_SET_EMOJI context.user_data["emoji"] = emoji await update.message.reply_text( "🖼 **Schritt 4/4**\n\n" "Sende nun das erste Sticker-Bild:\n" "(PNG oder WebP, 512x512 Pixel empfohlen)" ) return CREATE_SET_STICKER async def create_set_sticker(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Get first sticker and create the set with optional background removal.""" if not update.message.photo and not update.message.document: await update.message.reply_text( "⚠️ Bitte sende ein Bild (Foto oder Dokument)." ) return CREATE_SET_STICKER # Get file if update.message.photo: file = await update.message.photo[-1].get_file() else: file = await update.message.document.get_file() name = context.user_data.get(SET_NAME) title = context.user_data.get(SET_TITLE) emoji = context.user_data.get("emoji", "🤖") # Check if API key is configured if REMOVE_BG_API_KEY: # Get current API usage current_usage = await db.get_monthly_api_usage("remove_bg") is_limit_reached = await should_show_api_limit_warning(REMOVE_BG_API_KEY, current_usage) # Show background removal options button_text = f"☁️ Remove.bg API ({current_usage}/{MAX_API_CALLS_PER_MONTH})" if is_limit_reached: button_text += " ⚠️ Limit erreicht" keyboard = InlineKeyboardMarkup([ [InlineKeyboardButton("🤖 Lokal entfernen (kostenlos)", callback_data="bg_local")], [InlineKeyboardButton(button_text, callback_data="bg_api")], [InlineKeyboardButton("⏭️ Ohne Entfernung", callback_data="bg_skip")], ]) await update.message.reply_text( "🎨 **Hintergrund entfernen?**\n\n" "Wähle eine Option:", reply_markup=keyboard, parse_mode="Markdown" ) # Store file for later processing context.user_data["pending_file"] = file context.user_data["pending_name"] = name context.user_data["pending_title"] = title context.user_data["pending_emoji"] = emoji return REMOVE_BG_CHOICE else: # No API key - process directly await update.message.reply_text("⏳ Erstelle Stickerpaket...") success, result = await create_sticker_set( update, context, name, title, file, emoji ) if success: await update.message.reply_text( f"✅ **Stickerpaket erstellt!**\n\n" f"📦 Titel: {title}\n" f"🔗 Name: `{result}`\n\n" f"Nutze /start um weitere Aktionen durchzuführen.", parse_mode="Markdown" ) else: await update.message.reply_text( f"❌ **Fehler beim Erstellen:**\n{result}\n\n" f"Mögliche Ursachen:\n" f"• Name bereits vergeben\n" f"• Ungültiges Bildformat\n" f"• Bild zu groß/klein" ) # Cleanup context.user_data.pop(SET_NAME, None) context.user_data.pop(SET_TITLE, None) context.user_data.pop("emoji", None) return ConversationHandler.END async def bg_choice_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Handle background removal choice.""" query = update.callback_query await query.answer() data = query.data file = context.user_data.get("pending_file") name = context.user_data.get("pending_name") title = context.user_data.get("pending_title") emoji = context.user_data.get("pending_emoji") if not file: await query.edit_message_text("❌ Fehler: Kein Bild gefunden. Starte neu mit /start") return ConversationHandler.END if data == "bg_local": # Local background removal await query.edit_message_text("⏳ Entferne Hintergrund (lokal)...") try: # Download file file_obj = await file.download_as_bytearray() file_bytes = bytes(file_obj) # Validate image is_valid, error_msg = await validate_image_for_bg_removal(file_bytes) if not is_valid: await query.edit_message_text(f"❌ **Fehler:**\n{error_msg}\n\nVersuche es mit einem anderen Bild.") return ConversationHandler.END # Remove background result = await remove_background_local(file_bytes) if result: await query.edit_message_text("⏳ Erstelle Stickerpaket...") # Create sticker set with processed image from image_processor import process_image_for_sticker processed = await process_image_for_sticker(result) if processed: from telegram import InputSticker from telegram.constants import StickerFormat sticker = InputSticker( sticker=processed, format=StickerFormat.STATIC, emoji_list=[emoji] ) set_name = f"{name}_by_{context.bot.username}" await context.bot.create_new_sticker_set( user_id=update.effective_user.id, name=set_name, title=title, stickers=[sticker] ) await db.add_sticker_set(update.effective_user.id, set_name, title) await query.edit_message_text( f"✅ **Stickerpaket erstellt!**\n\n" f"📦 Titel: {title}\n" f"🔗 Name: `{set_name}`\n\n" f"Nutze /start um weitere Aktionen durchzuführen.", parse_mode="Markdown" ) else: await query.edit_message_text("❌ Fehler bei der Bildverarbeitung.") else: await query.edit_message_text( "❌ **Hintergrundentfernung fehlgeschlagen**\n\n" "Nutze /start um neu zu beginnen." ) except Exception as e: logger.error(f"Error in local bg removal: {e}") await query.edit_message_text(f"❌ Fehler: {str(e)}") return ConversationHandler.END elif data == "bg_api": # API background removal await query.edit_message_text("⏳ Verbinde mit Remove.bg API...") try: # Download file file_obj = await file.download_as_bytearray() file_bytes = bytes(file_obj) # Validate image is_valid, error_msg = await validate_image_for_bg_removal(file_bytes) if not is_valid: await query.edit_message_text(f"❌ **Fehler:**\n{error_msg}\n\nVersuche es mit einem anderen Bild.") return ConversationHandler.END # Call API with retry logic result, message = await remove_background_api(file_bytes, REMOVE_BG_API_KEY) if result == "LIMIT_REACHED" or result == "API_ERROR": # Show error with fallback options current_usage = await db.get_monthly_api_usage("remove_bg") keyboard = InlineKeyboardMarkup([ [InlineKeyboardButton("🤖 Stattdessen lokal entfernen", callback_data="bg_local")], [InlineKeyboardButton("⏭️ Ohne Entfernung", callback_data="bg_skip")], [InlineKeyboardButton("❌ Abbrechen", callback_data="bg_cancel")], ]) warning_text = "" if result == "LIMIT_REACHED": warning_text = f"⚠️ **API-Limit erreicht** ({current_usage}/{MAX_API_CALLS_PER_MONTH})\n\n" else: warning_text = f"⚠️ **API-Fehler:** {message}\n\n" await query.edit_message_text( warning_text + "Was möchtest du stattdessen tun?", reply_markup=keyboard, parse_mode="Markdown" ) return REMOVE_BG_API_ERROR # Success - track API call await db.track_api_call("remove_bg") await query.edit_message_text("⏳ Erstelle Stickerpaket...") # Process result from image_processor import process_image_for_sticker processed = await process_image_for_sticker(result) if processed: from telegram import InputSticker from telegram.constants import StickerFormat set_name = f"{name}_by_{context.bot.username}" sticker = InputSticker( sticker=processed, format=StickerFormat.STATIC, emoji_list=[emoji] ) await context.bot.create_new_sticker_set( user_id=update.effective_user.id, name=set_name, title=title, stickers=[sticker] ) await db.add_sticker_set(update.effective_user.id, set_name, title) await query.edit_message_text( f"✅ **Stickerpaket erstellt!**\n\n" f"📦 Titel: {title}\n" f"🔗 Name: `{set_name}`\n\n" f"Hintergrund entfernt mit Remove.bg API\n" f"Nutze /start um weitere Aktionen durchzuführen.", parse_mode="Markdown" ) else: await query.edit_message_text("❌ Fehler bei der Bildverarbeitung.") except Exception as e: logger.error(f"Error in API bg removal: {e}") await query.edit_message_text(f"❌ Fehler: {str(e)}") return ConversationHandler.END elif data == "bg_skip": # Skip background removal await query.edit_message_text("⏳ Erstelle Stickerpaket...") try: from telegram import InputSticker from telegram.constants import StickerFormat file_obj = await file.download_as_bytearray() file_bytes = bytes(file_obj) from image_processor import process_image_for_sticker processed = await process_image_for_sticker(file_bytes) if processed: set_name = f"{name}_by_{context.bot.username}" sticker = InputSticker( sticker=processed, format=StickerFormat.STATIC, emoji_list=[emoji] ) await context.bot.create_new_sticker_set( user_id=update.effective_user.id, name=set_name, title=title, stickers=[sticker] ) await db.add_sticker_set(update.effective_user.id, set_name, title) await query.edit_message_text( f"✅ **Stickerpaket erstellt!**\n\n" f"📦 Titel: {title}\n" f"🔗 Name: `{set_name}`\n\n" f"Nutze /start um weitere Aktionen durchzuführen.", parse_mode="Markdown" ) else: await query.edit_message_text("❌ Fehler bei der Bildverarbeitung.") except Exception as e: logger.error(f"Error creating sticker set: {e}") await query.edit_message_text(f"❌ Fehler: {str(e)}") return ConversationHandler.END elif data == "bg_cancel": await query.edit_message_text("❌ Abgebrochen. Nutze /start zum Neustart.") return ConversationHandler.END return ConversationHandler.END async def select_set_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Handle sticker set selection.""" query = update.callback_query await query.answer() data = query.data if data.startswith("select_set:"): set_name = data.split(":", 1)[1] context.user_data[CURRENT_SET] = set_name # Get set info from database sets = await db.get_user_sticker_sets(update.effective_user.id) set_info = next((s for s in sets if s['set_name'] == set_name), None) title = set_info['set_title'] if set_info else set_name await query.edit_message_text( f"📦 **Stickerpaket**\n" f"📝 Titel: {title}\n" f"🔗 Name: `{set_name}`\n\n" f"Was möchtest du tun?", reply_markup=get_set_action_keyboard(set_name), parse_mode="Markdown" ) return SET_ACTION elif data == "back_to_menu": return await menu_callback(update, context) return SELECT_SET async def set_action_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Handle set action selection.""" query = update.callback_query await query.answer() data = query.data set_name = context.user_data.get(CURRENT_SET) if data.startswith("add_sticker:"): await query.edit_message_text( "🖼 Sende ein neues Sticker-Bild:\n\n" "(PNG oder WebP, 512x512 Pixel empfohlen)" ) return ADD_STICKER elif data.startswith("delete_sticker:"): await query.edit_message_text( "🗑 Sende den Sticker, den du löschen möchtest:\n\n" "(Klicke auf einen Sticker und wähle 'Weiterleiten' zu diesem Bot)" ) return DELETE_STICKER elif data.startswith("change_emoji:"): await query.edit_message_text( "🗑 Sende den Sticker, dessen Emoji du ändern möchtest:\n\n" "(Klicke auf einen Sticker und wähle 'Weiterleiten')" ) return CHANGE_EMOJI elif data.startswith("rename_set:"): await query.edit_message_text( "✏️ Gib den neuen Titel für das Paket ein:" ) return RENAME_SET elif data.startswith("delete_set:"): keyboard = InlineKeyboardMarkup([ [InlineKeyboardButton("✅ Ja, löschen", callback_data="confirm_delete")], [InlineKeyboardButton("❌ Abbrechen", callback_data="cancel_delete")], ]) await query.edit_message_text( f"⚠️ **Bist du sicher?**\n\n" f"Das Stickerpaket wird unwiderruflich gelöscht!", reply_markup=keyboard, parse_mode="Markdown" ) return DELETE_CONFIRM elif data == "back_to_menu": return await menu_callback(update, context) return SET_ACTION async def add_sticker_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Handle new sticker addition - including GIFs.""" set_name = context.user_data.get(CURRENT_SET) # Check for GIF/Animation if update.message.animation: # It's a GIF/animation animation = update.message.animation # Download the file file = await animation.get_file() file_obj = await file.download_as_bytearray() gif_bytes = bytes(file_obj) # Get GIF info gif_info = await get_gif_info(gif_bytes) duration = gif_info.get('duration', 0) # Check if duration exceeds limit is_valid, error_msg = await validate_gif_duration(duration) if not is_valid: # GIF too long - ask user context.user_data[PENDING_ANIMATION] = { 'bytes': gif_bytes, 'duration': duration } keyboard = InlineKeyboardMarkup([ [InlineKeyboardButton("✂️ Auf 3 Sekunden kürzen", callback_data="trim_gif")], [InlineKeyboardButton("❌ Abbrechen", callback_data="cancel_gif")], ]) await update.message.reply_text( f"🎬 **GIF zu lang**\n\n" f"Dauer: {duration:.1f} Sekunden\n" f"Maximal erlaubt: 3 Sekunden\n\n" f"Was möchtest du tun?", reply_markup=keyboard, parse_mode="Markdown" ) return GIF_DURATION_CONFIRM # GIF is within limits - proceed with conversion context.user_data[PENDING_ANIMATION] = {'bytes': gif_bytes, 'duration': duration} await update.message.reply_text("🏷 Gib ein Emoji für den animierten Sticker ein (z.B. 😊):") return ADD_ANIMATION_EMOJI # Handle regular images if not update.message.photo and not update.message.document: await update.message.reply_text("⚠️ Bitte sende ein Bild oder GIF.") return ADD_STICKER # Get file if update.message.photo: file = await update.message.photo[-1].get_file() else: file = await update.message.document.get_file() # Store file for next step (emoji selection) context.user_data["pending_sticker"] = file await update.message.reply_text( "🏷 Gib ein Emoji für den neuen Sticker ein (z.B. 😊):" ) return ADD_STICKER_EMOJI async def gif_duration_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Handle GIF duration confirmation callback.""" query = update.callback_query await query.answer() if query.data == "trim_gif": await query.edit_message_text( "✂️ GIF wird auf 3 Sekunden gekürzt...\n\n" "🏷 Gib ein Emoji für den animierten Sticker ein (z.B. 😊):" ) return ADD_ANIMATION_EMOJI elif query.data == "cancel_gif": await query.edit_message_text("❌ GIF-Hinzufügen abgebrochen.") context.user_data.pop(PENDING_ANIMATION, None) return ConversationHandler.END return GIF_DURATION_CONFIRM async def add_animation_emoji(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Get emoji for animated sticker and process GIF.""" emoji = update.message.text.strip() set_name = context.user_data.get(CURRENT_SET) anim_data = context.user_data.get(PENDING_ANIMATION) if not anim_data: await update.message.reply_text("❌ Fehler: Kein GIF gefunden. Starte neu mit /start") return ConversationHandler.END gif_bytes = anim_data['bytes'] duration = anim_data['duration'] # Determine max duration max_duration = 3.0 if duration > 3.0 else None await update.message.reply_text("⏳ Konvertiere GIF zu Video Sticker...") # Convert GIF to WebM webm_bytes = await convert_gif_to_webm(gif_bytes, max_duration) if not webm_bytes: await update.message.reply_text( "❌ **Fehler bei der Konvertierung**\n\n" "Das GIF konnte nicht konvertiert werden.\n" "Mögliche Ursachen:\n" "• Datei zu groß\n" "• Ungültiges Format\n" "• Komprimierung fehlgeschlagen" ) context.user_data.pop(PENDING_ANIMATION, None) return ConversationHandler.END await update.message.reply_text("⏳ Füge animierten Sticker hinzu...") # Add as video sticker success = await add_video_sticker_to_set(update, context, set_name, webm_bytes, emoji) if success: await update.message.reply_text(f"✅ Animierter Sticker hinzugefügt mit Emoji {emoji}!") else: await update.message.reply_text("❌ Fehler beim Hinzufügen des animierten Stickers.") context.user_data.pop(PENDING_ANIMATION, None) return ConversationHandler.END async def add_existing_set_name(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Handle existing sticker set name input.""" set_name_input = update.message.text.strip() # Auto-append _by_ if not present if "_by_" not in set_name_input: bot_username = context.bot.username set_name = f"{set_name_input}_by_{bot_username}" else: set_name = set_name_input await update.message.reply_text(f"⏳ Suche Stickerpaket `{set_name}`...", parse_mode="Markdown") # Try to add existing set success, message = await add_existing_sticker_set(update, context, set_name) if success: await update.message.reply_text( f"✅ {message}\n\n" f"Nutze /start um das Paket zu verwalten.", parse_mode="Markdown" ) else: await update.message.reply_text( f"❌ **Fehler:**\n{message}\n\n" f"**Tipps:**\n" f"• Prüfe den Namen (z.B. `meine_sticker` oder `meine_sticker_by_botname`)\n" f"• Das Paket muss öffentlich sein\n" f"• Du musst Besitzer des Pakets sein\n\n" f"Versuche es erneut oder nutze /start.", parse_mode="Markdown" ) return ConversationHandler.END async def add_sticker_emoji(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Get emoji for new sticker and add it.""" emoji = update.message.text.strip() set_name = context.user_data.get(CURRENT_SET) file = context.user_data.get("pending_sticker") if not file: await update.message.reply_text("❌ Fehler: Kein Sticker gefunden. Starte neu mit /start") return ConversationHandler.END await update.message.reply_text("⏳ Füge Sticker hinzu...") success = await add_sticker_to_set(update, context, set_name, file, emoji) if success: await update.message.reply_text(f"✅ Sticker hinzugefügt mit Emoji {emoji}!") else: await update.message.reply_text("❌ Fehler beim Hinzufügen des Stickers.") context.user_data.pop("pending_sticker", None) return ConversationHandler.END async def delete_sticker_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Handle sticker deletion.""" if not update.message.sticker: await update.message.reply_text("⚠️ Bitte sende einen Sticker (nicht ein Bild).") return DELETE_STICKER sticker_file_id = update.message.sticker.file_id await update.message.reply_text("⏳ Lösche Sticker...") success = await delete_sticker_from_set(update, context, sticker_file_id) if success: await update.message.reply_text("✅ Sticker gelöscht!") else: await update.message.reply_text("❌ Fehler beim Löschen des Stickers.") return ConversationHandler.END async def change_emoji_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Handle sticker emoji change - step 1: get sticker.""" if not update.message.sticker: await update.message.reply_text("⚠️ Bitte sende einen Sticker.") return CHANGE_EMOJI sticker_file_id = update.message.sticker.file_id context.user_data[STICKER_TO_CHANGE] = sticker_file_id await update.message.reply_text( "🏷 Gib das neue Emoji für den Sticker ein (z.B. 😊):" ) return CHANGE_EMOJI async def change_emoji_input(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Handle new emoji input.""" new_emoji = update.message.text.strip() sticker_file_id = context.user_data.get(STICKER_TO_CHANGE) if not sticker_file_id: await update.message.reply_text("❌ Fehler. Starte neu mit /start") return ConversationHandler.END await update.message.reply_text("⏳ Ändere Emoji...") success = await change_sticker_emoji(update, context, sticker_file_id, new_emoji) if success: await update.message.reply_text(f"✅ Emoji geändert zu {new_emoji}!") else: await update.message.reply_text("❌ Fehler beim Ändern des Emojis.") context.user_data.pop(STICKER_TO_CHANGE, None) return ConversationHandler.END async def rename_set_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Handle sticker set renaming.""" set_name = context.user_data.get(CURRENT_SET) new_title = update.message.text.strip() if len(new_title) > 64: await update.message.reply_text("⚠️ Titel zu lang (max. 64 Zeichen). Versuche es erneut:") return RENAME_SET await update.message.reply_text("⏳ Benenne Paket um...") success = await rename_sticker_set(update, context, set_name, new_title) if success: await update.message.reply_text(f"✅ Paket umbenannt in: {new_title}") else: await update.message.reply_text("❌ Fehler beim Umbenennen des Pakets.") return ConversationHandler.END async def delete_confirm_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Handle set deletion confirmation.""" query = update.callback_query await query.answer() if query.data == "confirm_delete": set_name = context.user_data.get(CURRENT_SET) await query.edit_message_text("⏳ Lösche Paket...") success = await delete_sticker_set(update, context, set_name) if success: await query.edit_message_text("✅ Stickerpaket gelöscht!") else: await query.edit_message_text("❌ Fehler beim Löschen des Pakets.") context.user_data.pop(CURRENT_SET, None) return ConversationHandler.END elif query.data == "cancel_delete": set_name = context.user_data.get(CURRENT_SET) sets = await db.get_user_sticker_sets(update.effective_user.id) set_info = next((s for s in sets if s['set_name'] == set_name), None) title = set_info['set_title'] if set_info else set_name await query.edit_message_text( f"📦 **Stickerpaket**\n" f"📝 Titel: {title}\n" f"🔗 Name: `{set_name}`\n\n" f"Was möchtest du tun?", reply_markup=get_set_action_keyboard(set_name), parse_mode="Markdown" ) return SET_ACTION return ConversationHandler.END async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Cancel conversation.""" await update.message.reply_text( "❌ Aktion abgebrochen. Nutze /start zum Neustart." ) # Cleanup keys_to_remove = [SET_NAME, SET_TITLE, CURRENT_SET, STICKER_TO_CHANGE, PENDING_ANIMATION, "pending_file", "pending_name", "pending_title", "pending_emoji", "emoji", "pending_sticker"] for key in keys_to_remove: context.user_data.pop(key, None) return ConversationHandler.END async def post_init(application: Application): """Initialize database and schedule jobs after bot startup.""" await db.init() # Schedule premium check job if application.job_queue: application.job_queue.run_repeating( schedule_premium_check, interval=PREMIUM_CHECK_INTERVAL * 3600, # Convert to seconds first=10 # First check after 10 seconds ) logger.info(f"Scheduled premium check every {PREMIUM_CHECK_INTERVAL} hours") def main(): """Run the bot.""" application = Application.builder().token(BOT_TOKEN).build() # Add post_init callback application.post_init = post_init conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ MENU: [ CallbackQueryHandler(menu_callback, pattern="^(create_set|list_sets|help|back_to_menu)$"), ], CREATE_SET_NAME: [ MessageHandler(filters.TEXT & ~filters.COMMAND, create_set_name), ], CREATE_SET_TITLE: [ MessageHandler(filters.TEXT & ~filters.COMMAND, create_set_title), ], CREATE_SET_EMOJI: [ MessageHandler(filters.TEXT & ~filters.COMMAND, create_set_emoji), ], CREATE_SET_STICKER: [ MessageHandler(filters.PHOTO | filters.Document.ALL, create_set_sticker), ], SELECT_SET: [ CallbackQueryHandler(select_set_callback, pattern="^(select_set:|back_to_menu)$"), ], SET_ACTION: [ CallbackQueryHandler(set_action_callback, pattern="^(add_sticker:|delete_sticker:|change_emoji:|rename_set:|delete_set:|back_to_menu)$"), ], ADD_STICKER: [ MessageHandler(filters.PHOTO | filters.Document.ALL | filters.ANIMATION, add_sticker_handler), ], ADD_STICKER_EMOJI: [ MessageHandler(filters.TEXT & ~filters.COMMAND, add_sticker_emoji), ], GIF_DURATION_CONFIRM: [ CallbackQueryHandler(gif_duration_callback, pattern="^(trim_gif|cancel_gif)$"), ], ADD_ANIMATION_EMOJI: [ MessageHandler(filters.TEXT & ~filters.COMMAND, add_animation_emoji), ], DELETE_STICKER: [ MessageHandler(filters.Sticker.ALL, delete_sticker_handler), ], CHANGE_EMOJI: [ MessageHandler(filters.Sticker.ALL, change_emoji_handler), ], RENAME_SET: [ MessageHandler(filters.TEXT & ~filters.COMMAND, rename_set_handler), ], DELETE_CONFIRM: [ CallbackQueryHandler(delete_confirm_callback, pattern="^(confirm_delete|cancel_delete)$"), ], REMOVE_BG_CHOICE: [ CallbackQueryHandler(bg_choice_callback, pattern="^(bg_local|bg_api|bg_skip|bg_cancel)$"), ], REMOVE_BG_API_ERROR: [ CallbackQueryHandler(bg_choice_callback, pattern="^(bg_local|bg_skip|bg_cancel)$"), ], }, fallbacks=[CommandHandler("cancel", cancel)], ) application.add_handler(conv_handler) logger.info("Starting sticker bot...") application.run_polling(allowed_updates=Update.ALL_TYPES) if __name__ == "__main__": main() async def hilfe_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """Send help message.""" if not await check_owner(update): await update.message.reply_text("⛔️ Dieser Bot ist nur für den Besitzer verfügbar.") return help_text = ( "❓ **Hilfe - Sticker Bot Manager**\n\n" "**📱 Befehle:**\n" "• /start - Bot starten\n" "• /hilfe - Diese Hilfe anzeigen\n" "• /cancel - Aktuelle Aktion abbrechen\n\n" "**🎨 Funktionen:**\n" "1️⃣ **Neues Stickerpaket**\n" " - Name → Titel → Emoji → Bild\n" " - Bilder werden automatisch optimiert\n\n" "2️⃣ **Sticker hinzufügen**\n" " - Bilder: PNG, JPG, WebP (automatische Konvertierung)\n" " - GIFs: Werden zu animierten Video-Stickern (max. 3s)\n" " - Optional: Hintergrund automatisch entfernen\n\n" "3️⃣ **Sticker löschen**\n" " - Sende den Sticker direkt an den Bot\n\n" "4️⃣ **Emoji ändern**\n" " - Sticker senden → Neuen Emoji eingeben\n\n" "5️⃣ **Paket umbenennen**\n" " - Neuen Titel eingeben (max. 64 Zeichen)\n\n" "6️⃣ **Paket löschen**\n" " - ⚠️ Kann nicht rückgängig gemacht werden!\n\n" "**💡 Tipps:**\n" "• Bilder als Datei senden für beste Qualität\n" "• Ideale Größe: 512x512 Pixel\n" "• Max. 120 Sticker pro reguläres Paket\n" "• Max. 200 Sticker pro Emoji-Paket\n\n" "**🎨 Hintergrundentfernung:**\n" "• Lokal: Kostenlos, direkt auf dem Server\n" "• Remove.bg API: 50 Aufrufe/Monat gratis\n\n" "Nutze /start zum Öffnen des Hauptmenüs." ) await update.message.reply_text(help_text, parse_mode="Markdown") def main(): """Run the bot.""" application = Application.builder().token(BOT_TOKEN).build() # Add post_init callback application.post_init = post_init # Add standalone command handlers application.add_handler(CommandHandler("hilfe", hilfe_command)) conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ MENU: [ CallbackQueryHandler(menu_callback, pattern="^(create_set|list_sets|help|back_to_menu)$"), ], CREATE_SET_NAME: [ MessageHandler(filters.TEXT & ~filters.COMMAND, create_set_name), ], CREATE_SET_TITLE: [ MessageHandler(filters.TEXT & ~filters.COMMAND, create_set_title), ], CREATE_SET_EMOJI: [ MessageHandler(filters.TEXT & ~filters.COMMAND, create_set_emoji), ], CREATE_SET_STICKER: [ MessageHandler(filters.PHOTO | filters.Document.ALL, create_set_sticker), ], SELECT_SET: [ CallbackQueryHandler(select_set_callback, pattern="^(select_set:|back_to_menu)$"), ], SET_ACTION: [ CallbackQueryHandler(set_action_callback, pattern="^(add_sticker:|delete_sticker:|change_emoji:|rename_set:|delete_set:|back_to_menu)$"), ], ADD_STICKER: [ MessageHandler(filters.PHOTO | filters.Document.ALL | filters.ANIMATION, add_sticker_handler), ], ADD_STICKER_EMOJI: [ MessageHandler(filters.TEXT & ~filters.COMMAND, add_sticker_emoji), ], GIF_DURATION_CONFIRM: [ CallbackQueryHandler(gif_duration_callback, pattern="^(trim_gif|cancel_gif)$"), ], ADD_ANIMATION_EMOJI: [ MessageHandler(filters.TEXT & ~filters.COMMAND, add_animation_emoji), ], DELETE_STICKER: [ MessageHandler(filters.Sticker.ALL, delete_sticker_handler), ], CHANGE_EMOJI: [ MessageHandler(filters.Sticker.ALL, change_emoji_handler), ], RENAME_SET: [ MessageHandler(filters.TEXT & ~filters.COMMAND, rename_set_handler), ], DELETE_CONFIRM: [ CallbackQueryHandler(delete_confirm_callback, pattern="^(confirm_delete|cancel_delete)$"), ], REMOVE_BG_CHOICE: [ CallbackQueryHandler(bg_choice_callback, pattern="^(bg_local|bg_api|bg_skip|bg_cancel)$"), ], REMOVE_BG_API_ERROR: [ CallbackQueryHandler(bg_choice_callback, pattern="^(bg_local|bg_skip|bg_cancel)$"), ], }, fallbacks=[CommandHandler("cancel", cancel)], ) application.add_handler(conv_handler) logger.info("Starting sticker bot...") application.run_polling(allowed_updates=Update.ALL_TYPES) if __name__ == "__main__": main()