Fix yt-dlp title error, and inline title
This commit is contained in:
@@ -214,7 +214,15 @@ async def callback_handler(client: Client, callback_query: CallbackQuery):
|
||||
|
||||
elif data.startswith("video_select:"):
|
||||
# Handle video selection from webpage
|
||||
video_url = data.replace("video_select:", "", 1)
|
||||
selection_id = data.replace("video_select:", "", 1)
|
||||
|
||||
# Retrieve video URL from cache using short identifier
|
||||
from bot.modules.message_handler.video_selection_cache import get_video_selection
|
||||
video_url = get_video_selection(selection_id, user_id=user_id)
|
||||
|
||||
if not video_url:
|
||||
await callback_query.answer("❌ Ссылка истекла или не найдена. Пожалуйста, попробуйте снова.", show_alert=True)
|
||||
return
|
||||
|
||||
# Create task for selected video
|
||||
try:
|
||||
|
||||
@@ -588,6 +588,8 @@ async def url_handler(client: Client, message: Message):
|
||||
text += "Выберите видео для загрузки:\n\n"
|
||||
|
||||
# Create inline keyboard with video selection buttons
|
||||
from bot.modules.message_handler.video_selection_cache import store_video_selection
|
||||
|
||||
buttons = []
|
||||
for idx, video in enumerate(videos[:10], 1): # Limit to 10 videos
|
||||
title = video.get('title', f'Видео {idx}')[:50] # Limit title length
|
||||
@@ -597,8 +599,10 @@ async def url_handler(client: Client, message: Message):
|
||||
duration_str = format_duration(duration)
|
||||
title += f" ({duration_str})"
|
||||
|
||||
# Use callback data format: video_select:<video_url>
|
||||
callback_data = f"video_select:{video['url']}"
|
||||
# Store video URL in cache and use short identifier in callback_data
|
||||
# This avoids Telegram's 64-byte limit on callback_data
|
||||
selection_id = store_video_selection(video['url'], user_id)
|
||||
callback_data = f"video_select:{selection_id}"
|
||||
buttons.append([InlineKeyboardButton(f"{idx}. {title}", callback_data=callback_data)])
|
||||
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
@@ -779,4 +783,3 @@ def register_commands(app: Client):
|
||||
app.add_handler(MessageHandler(url_handler, filters=is_url_message))
|
||||
|
||||
logger.info("Commands registered")
|
||||
|
||||
|
||||
129
bot/modules/message_handler/video_selection_cache.py
Normal file
129
bot/modules/message_handler/video_selection_cache.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
Video selection cache for callback handlers
|
||||
|
||||
Stores mappings between short identifiers and video URLs to work around
|
||||
Telegram's 64-byte limit on callback_data.
|
||||
"""
|
||||
import secrets
|
||||
import threading
|
||||
import time
|
||||
from typing import Optional, Dict
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Dictionary to store video URL mappings
|
||||
# Format: {selection_id: {'url': str, 'user_id': int, 'created_at': float}}
|
||||
_video_selections: Dict[str, Dict] = {}
|
||||
_video_selections_lock = threading.Lock()
|
||||
_MAX_SELECTIONS = 10000 # Maximum number of cached selections
|
||||
_SELECTION_TTL = 3600 # Time to live: 1 hour in seconds
|
||||
|
||||
|
||||
def generate_selection_id() -> str:
|
||||
"""Generate a short unique identifier for video selection"""
|
||||
return secrets.token_urlsafe(8) # ~11 characters, well under 64 bytes
|
||||
|
||||
|
||||
def store_video_selection(url: str, user_id: int) -> str:
|
||||
"""
|
||||
Store a video URL and return a short identifier
|
||||
|
||||
Args:
|
||||
url: Video URL to store
|
||||
user_id: User ID who requested the selection
|
||||
|
||||
Returns:
|
||||
Short identifier to use in callback_data
|
||||
"""
|
||||
selection_id = generate_selection_id()
|
||||
|
||||
with _video_selections_lock:
|
||||
# Clean up old entries if cache is too large
|
||||
if len(_video_selections) >= _MAX_SELECTIONS:
|
||||
_cleanup_expired_selections()
|
||||
|
||||
# If still too large, remove oldest entries
|
||||
if len(_video_selections) >= _MAX_SELECTIONS:
|
||||
# Remove 10% of oldest entries
|
||||
sorted_items = sorted(
|
||||
_video_selections.items(),
|
||||
key=lambda x: x[1].get('created_at', 0)
|
||||
)
|
||||
items_to_remove = len(sorted_items) // 10
|
||||
for item_id, _ in sorted_items[:items_to_remove]:
|
||||
del _video_selections[item_id]
|
||||
|
||||
_video_selections[selection_id] = {
|
||||
'url': url,
|
||||
'user_id': user_id,
|
||||
'created_at': time.time()
|
||||
}
|
||||
|
||||
logger.debug(f"Stored video selection: {selection_id} for user {user_id}")
|
||||
return selection_id
|
||||
|
||||
|
||||
def get_video_selection(selection_id: str, user_id: Optional[int] = None) -> Optional[str]:
|
||||
"""
|
||||
Retrieve a video URL by selection identifier
|
||||
|
||||
Args:
|
||||
selection_id: Short identifier from callback_data
|
||||
user_id: Optional user ID to verify ownership
|
||||
|
||||
Returns:
|
||||
Video URL or None if not found or expired
|
||||
"""
|
||||
with _video_selections_lock:
|
||||
selection = _video_selections.get(selection_id)
|
||||
|
||||
if not selection:
|
||||
logger.debug(f"Selection not found: {selection_id}")
|
||||
return None
|
||||
|
||||
# Check if expired
|
||||
if time.time() - selection['created_at'] > _SELECTION_TTL:
|
||||
del _video_selections[selection_id]
|
||||
logger.debug(f"Selection expired: {selection_id}")
|
||||
return None
|
||||
|
||||
# Verify user ownership if provided
|
||||
if user_id is not None and selection['user_id'] != user_id:
|
||||
logger.warning(
|
||||
f"User {user_id} attempted to access selection {selection_id} "
|
||||
f"owned by user {selection['user_id']}"
|
||||
)
|
||||
return None
|
||||
|
||||
return selection['url']
|
||||
|
||||
|
||||
def _cleanup_expired_selections():
|
||||
"""Remove expired selections from cache"""
|
||||
current_time = time.time()
|
||||
expired_ids = [
|
||||
sel_id for sel_id, sel_data in _video_selections.items()
|
||||
if current_time - sel_data['created_at'] > _SELECTION_TTL
|
||||
]
|
||||
|
||||
for sel_id in expired_ids:
|
||||
del _video_selections[sel_id]
|
||||
|
||||
if expired_ids:
|
||||
logger.debug(f"Cleaned up {len(expired_ids)} expired selections")
|
||||
|
||||
|
||||
def clear_user_selections(user_id: int):
|
||||
"""Clear all selections for a specific user"""
|
||||
with _video_selections_lock:
|
||||
to_remove = [
|
||||
sel_id for sel_id, sel_data in _video_selections.items()
|
||||
if sel_data['user_id'] == user_id
|
||||
]
|
||||
for sel_id in to_remove:
|
||||
del _video_selections[sel_id]
|
||||
|
||||
if to_remove:
|
||||
logger.debug(f"Cleared {len(to_remove)} selections for user {user_id}")
|
||||
|
||||
Reference in New Issue
Block a user