Files
stickerbot/sticker_manager.py
coding f634b884ec Initial commit: Telegram Sticker Bot Manager
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
2026-03-25 08:59:38 +00:00

310 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Sticker management utilities for Telegram bot."""
import logging
from typing import List, Optional, Tuple, Dict, Any
from telegram import InlineKeyboardMarkup, InlineKeyboardButton, Update
from telegram.ext import ContextTypes
from telegram.constants import StickerFormat
from database import db
from image_processor import process_image_for_sticker, validate_image
logger = logging.getLogger(__name__)
async def check_owner(update: Update) -> bool:
"""Check if user is the bot owner."""
from config import OWNER_ID
user_id = update.effective_user.id
return user_id == OWNER_ID
async def check_premium_status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
"""Check if user has Telegram Premium."""
user_id = update.effective_user.id
# Get current user info from Telegram
try:
chat = await context.bot.get_chat(user_id)
is_premium = bool(getattr(chat, 'is_premium', False))
# Update database
await db.update_premium_status(user_id, is_premium)
return is_premium
except Exception as e:
logger.error(f"Error checking premium status: {e}")
# Fallback to cached status
cached = await db.get_premium_status(user_id)
return cached if cached is not None else False
def get_main_menu_keyboard() -> InlineKeyboardMarkup:
"""Generate main menu keyboard."""
keyboard = [
[InlineKeyboardButton(" Neues Stickerpaket", callback_data="create_set")],
[InlineKeyboardButton("📥 Existierendes hinzufügen", callback_data="add_existing")],
[InlineKeyboardButton("📦 Meine Stickerpakete", callback_data="list_sets")],
[InlineKeyboardButton("❓ Hilfe", callback_data="help")],
]
return InlineKeyboardMarkup(keyboard)
def get_set_action_keyboard(set_name: str) -> InlineKeyboardMarkup:
"""Generate keyboard for set actions."""
keyboard = [
[InlineKeyboardButton(" Sticker hinzufügen", callback_data=f"add_sticker:{set_name}")],
[InlineKeyboardButton("🗑 Sticker löschen", callback_data=f"delete_sticker:{set_name}")],
[InlineKeyboardButton("🏷 Emoji ändern", callback_data=f"change_emoji:{set_name}")],
[InlineKeyboardButton("✏️ Paket umbenennen", callback_data=f"rename_set:{set_name}")],
[InlineKeyboardButton("🗑 Paket löschen", callback_data=f"delete_set:{set_name}")],
[InlineKeyboardButton("⬅️ Zurück", callback_data="back_to_menu")],
]
return InlineKeyboardMarkup(keyboard)
async def get_user_sets_keyboard(user_id: int) -> Tuple[Optional[InlineKeyboardMarkup], str]:
"""Generate keyboard with user's sticker sets."""
sets = await db.get_user_sticker_sets(user_id)
if not sets:
return None, "📭 Du hast noch keine Stickerpakete."
keyboard = []
for s in sets:
display_name = s['set_title'][:30] if s['set_title'] else s['set_name']
keyboard.append([InlineKeyboardButton(
f"📦 {display_name}",
callback_data=f"select_set:{s['set_name']}"
)])
keyboard.append([InlineKeyboardButton("⬅️ Zurück", callback_data="back_to_menu")])
text = f"📦 **Deine Stickerpakete** ({len(sets)} total)\\n\\nWähle ein Paket:"
return InlineKeyboardMarkup(keyboard), text
async def create_sticker_set(
update: Update,
context: ContextTypes.DEFAULT_TYPE,
name: str,
title: str,
sticker_file,
emoji: str = "🤖"
) -> Tuple[bool, str]:
"""Create a new sticker set with automatic image processing."""
try:
user_id = update.effective_user.id
bot_username = context.bot.username
set_name = f"{name}_by_{bot_username}"
# Download the file
file_obj = await sticker_file.download_as_bytearray()
file_bytes = bytes(file_obj)
# Validate image
is_valid, error_msg = await validate_image(file_bytes)
if not is_valid:
return False, f"Invalid image: {error_msg}"
# Process image for sticker format
processed_image = await process_image_for_sticker(file_bytes)
if not processed_image:
return False, "Failed to process image. Please ensure it's a valid image file (PNG, JPG, WebP) and under 10MB."
# Create sticker input with processed image
from telegram import InputSticker
sticker = InputSticker(
sticker=processed_image,
format=StickerFormat.STATIC,
emoji_list=[emoji]
)
# Create sticker set
await context.bot.create_new_sticker_set(
user_id=user_id,
name=set_name,
title=title,
stickers=[sticker]
)
# Track in database
await db.add_sticker_set(user_id, set_name, title)
return True, set_name
except Exception as e:
logger.error(f"Error creating sticker set: {e}")
return False, str(e)
async def add_sticker_to_set(
update: Update,
context: ContextTypes.DEFAULT_TYPE,
set_name: str,
sticker_file,
emoji: str = "🤖"
) -> bool:
"""Add a sticker to existing set with automatic image processing."""
try:
# Download and process the file
file_obj = await sticker_file.download_as_bytearray()
file_bytes = bytes(file_obj)
# Validate image
is_valid, error_msg = await validate_image(file_bytes)
if not is_valid:
logger.error(f"Invalid image for sticker: {error_msg}")
return False
# Process image for sticker format
processed_image = await process_image_for_sticker(file_bytes)
if not processed_image:
logger.error("Failed to process image for sticker")
return False
from telegram import InputSticker
sticker = InputSticker(
sticker=processed_image,
format=StickerFormat.STATIC,
emoji_list=[emoji]
)
await context.bot.add_sticker_to_set(
user_id=update.effective_user.id,
name=set_name,
sticker=sticker
)
return True
except Exception as e:
logger.error(f"Error adding sticker: {e}")
return False
async def add_video_sticker_to_set(
update: Update,
context: ContextTypes.DEFAULT_TYPE,
set_name: str,
webm_bytes: bytes,
emoji: str = "🤖"
) -> bool:
"""Add a video sticker (WebM) to existing set."""
try:
from telegram import InputSticker
sticker = InputSticker(
sticker=webm_bytes,
format=StickerFormat.VIDEO,
emoji_list=[emoji]
)
await context.bot.add_sticker_to_set(
user_id=update.effective_user.id,
name=set_name,
sticker=sticker
)
return True
except Exception as e:
logger.error(f"Error adding video sticker: {e}")
return False
async def delete_sticker_from_set(update: Update, context: ContextTypes.DEFAULT_TYPE,
sticker_file_id: str) -> bool:
"""Delete a sticker from a set."""
try:
await context.bot.delete_sticker_from_set(sticker_file_id)
await db.delete_sticker(sticker_file_id)
return True
except Exception as e:
logger.error(f"Error deleting sticker: {e}")
return False
async def delete_sticker_set(update: Update, context: ContextTypes.DEFAULT_TYPE,
set_name: str) -> bool:
"""Delete a sticker set."""
try:
await context.bot.delete_sticker_set(set_name)
await db.delete_sticker_set(set_name)
return True
except Exception as e:
logger.error(f"Error deleting sticker set: {e}")
return False
async def rename_sticker_set(update: Update, context: ContextTypes.DEFAULT_TYPE,
set_name: str, new_title: str) -> bool:
"""Rename a sticker set title."""
try:
await context.bot.set_sticker_set_title(set_name, new_title)
await db.rename_sticker_set(set_name, new_title)
return True
except Exception as e:
logger.error(f"Error renaming sticker set: {e}")
return False
async def change_sticker_emoji(update: Update, context: ContextTypes.DEFAULT_TYPE,
sticker_file_id: str, new_emoji: str) -> bool:
"""Change emoji of a sticker."""
try:
await context.bot.set_sticker_emoji_list(sticker_file_id, [new_emoji])
return True
except Exception as e:
logger.error(f"Error changing sticker emoji: {e}")
return False
async def add_existing_sticker_set(
update: Update,
context: ContextTypes.DEFAULT_TYPE,
set_name: str
) -> Tuple[bool, str]:
"""
Add an existing sticker set to the database.
Args:
set_name: Name of the sticker set (e.g., "my_stickers_by_mybot")
Returns:
Tuple of (success, message)
"""
try:
# Try to get sticker set from Telegram
sticker_set = await context.bot.get_sticker_set(set_name)
if not sticker_set:
return False, "Stickerpaket nicht gefunden."
# Get set info
title = sticker_set.title
user_id = update.effective_user.id
# Check if already in database
existing_sets = await db.get_user_sticker_sets(user_id)
if any(s['set_name'] == set_name for s in existing_sets):
return False, "Dieses Stickerpaket ist bereits in deiner Liste."
# Add to database
await db.add_sticker_set(user_id, set_name, title)
logger.info(f"Added existing sticker set '{set_name}' to database for user {user_id}")
return True, f"Stickerpaket '{title}' wurde hinzugefügt!"
except Exception as e:
logger.error(f"Error adding existing sticker set: {e}")
return False, f"Fehler: {str(e)}\n\nPrüfe, ob der Name korrekt ist und du Zugriff auf das Paket hast."
async def schedule_premium_check(context: ContextTypes.DEFAULT_TYPE):
"""Scheduled job to check premium status periodically."""
from config import OWNER_ID, logger
try:
# Check owner's premium status
chat = await context.bot.get_chat(OWNER_ID)
is_premium = bool(getattr(chat, 'is_premium', False))
await db.update_premium_status(OWNER_ID, is_premium)
logger.info(f"Premium check completed. Owner premium status: {is_premium}")
except Exception as e:
logger.error(f"Error in scheduled premium check: {e}")