Features: - Create and manage sticker packs - Add existing sticker sets to database - Automatic image optimization (512px, WebP conversion) - GIF to animated video sticker conversion (WebM, VP9) - Background removal (local rembg + optional Remove.bg API) - Hybrid API with retry logic and usage tracking - Premium status detection - SQLite database for persistence - Docker containerization - Complete OPENCODE.md documentation Technical Stack: - python-telegram-bot 22.7 - Pillow for image processing - FFmpeg for video conversion - rembg for local background removal - aiosqlite for async database operations
1158 lines
42 KiB
Python
1158 lines
42 KiB
Python
"""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_<bot_username> 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() |