chore: run lint and prettifier
This commit is contained in:
+156
-69
@@ -1,13 +1,15 @@
|
||||
# Bot utilities package
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Dict, Any
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from aiogram import types
|
||||
|
||||
|
||||
@dataclass
|
||||
class MessageContent:
|
||||
"""Класс для хранения информации о контенте сообщения"""
|
||||
|
||||
content_type: str
|
||||
file_id: Optional[str] = None
|
||||
text: Optional[str] = None
|
||||
@@ -15,15 +17,121 @@ class MessageContent:
|
||||
|
||||
# Словари поддерживаемых параметров для каждого типа сообщения
|
||||
SUPPORTED_PARAMS = {
|
||||
"text": {"parse_mode", "entities", "disable_web_page_preview", "disable_notification", "protect_content", "reply_markup", "reply_to_message_id", "allow_sending_without_reply", "message_thread_id"},
|
||||
"photo": {"caption", "parse_mode", "caption_entities", "disable_notification", "protect_content", "reply_markup", "reply_to_message_id", "allow_sending_without_reply", "message_thread_id", "has_spoiler"},
|
||||
"video": {"duration", "width", "height", "thumbnail", "caption", "parse_mode", "caption_entities", "supports_streaming", "disable_notification", "protect_content", "reply_markup", "reply_to_message_id", "allow_sending_without_reply", "message_thread_id", "has_spoiler"},
|
||||
"animation": {"duration", "width", "height", "thumbnail", "caption", "parse_mode", "caption_entities", "disable_notification", "protect_content", "reply_markup", "reply_to_message_id", "allow_sending_without_reply", "message_thread_id", "has_spoiler"},
|
||||
"document": {"thumbnail", "caption", "parse_mode", "caption_entities", "disable_content_type_detection", "disable_notification", "protect_content", "reply_markup", "reply_to_message_id", "allow_sending_without_reply", "message_thread_id"},
|
||||
"audio": {"caption", "parse_mode", "caption_entities", "duration", "performer", "title", "thumbnail", "disable_notification", "protect_content", "reply_markup", "reply_to_message_id", "allow_sending_without_reply", "message_thread_id"},
|
||||
"voice": {"caption", "parse_mode", "caption_entities", "duration", "disable_notification", "protect_content", "reply_markup", "reply_to_message_id", "allow_sending_without_reply", "message_thread_id"},
|
||||
"sticker": {"disable_notification", "protect_content", "reply_markup", "reply_to_message_id", "allow_sending_without_reply", "message_thread_id"},
|
||||
"video_note": {"duration", "length", "thumbnail", "disable_notification", "protect_content", "reply_markup", "reply_to_message_id", "allow_sending_without_reply", "message_thread_id"},
|
||||
"text": {
|
||||
"parse_mode",
|
||||
"entities",
|
||||
"disable_web_page_preview",
|
||||
"disable_notification",
|
||||
"protect_content",
|
||||
"reply_markup",
|
||||
"reply_to_message_id",
|
||||
"allow_sending_without_reply",
|
||||
"message_thread_id",
|
||||
},
|
||||
"photo": {
|
||||
"caption",
|
||||
"parse_mode",
|
||||
"caption_entities",
|
||||
"disable_notification",
|
||||
"protect_content",
|
||||
"reply_markup",
|
||||
"reply_to_message_id",
|
||||
"allow_sending_without_reply",
|
||||
"message_thread_id",
|
||||
"has_spoiler",
|
||||
},
|
||||
"video": {
|
||||
"duration",
|
||||
"width",
|
||||
"height",
|
||||
"thumbnail",
|
||||
"caption",
|
||||
"parse_mode",
|
||||
"caption_entities",
|
||||
"supports_streaming",
|
||||
"disable_notification",
|
||||
"protect_content",
|
||||
"reply_markup",
|
||||
"reply_to_message_id",
|
||||
"allow_sending_without_reply",
|
||||
"message_thread_id",
|
||||
"has_spoiler",
|
||||
},
|
||||
"animation": {
|
||||
"duration",
|
||||
"width",
|
||||
"height",
|
||||
"thumbnail",
|
||||
"caption",
|
||||
"parse_mode",
|
||||
"caption_entities",
|
||||
"disable_notification",
|
||||
"protect_content",
|
||||
"reply_markup",
|
||||
"reply_to_message_id",
|
||||
"allow_sending_without_reply",
|
||||
"message_thread_id",
|
||||
"has_spoiler",
|
||||
},
|
||||
"document": {
|
||||
"thumbnail",
|
||||
"caption",
|
||||
"parse_mode",
|
||||
"caption_entities",
|
||||
"disable_content_type_detection",
|
||||
"disable_notification",
|
||||
"protect_content",
|
||||
"reply_markup",
|
||||
"reply_to_message_id",
|
||||
"allow_sending_without_reply",
|
||||
"message_thread_id",
|
||||
},
|
||||
"audio": {
|
||||
"caption",
|
||||
"parse_mode",
|
||||
"caption_entities",
|
||||
"duration",
|
||||
"performer",
|
||||
"title",
|
||||
"thumbnail",
|
||||
"disable_notification",
|
||||
"protect_content",
|
||||
"reply_markup",
|
||||
"reply_to_message_id",
|
||||
"allow_sending_without_reply",
|
||||
"message_thread_id",
|
||||
},
|
||||
"voice": {
|
||||
"caption",
|
||||
"parse_mode",
|
||||
"caption_entities",
|
||||
"duration",
|
||||
"disable_notification",
|
||||
"protect_content",
|
||||
"reply_markup",
|
||||
"reply_to_message_id",
|
||||
"allow_sending_without_reply",
|
||||
"message_thread_id",
|
||||
},
|
||||
"sticker": {
|
||||
"disable_notification",
|
||||
"protect_content",
|
||||
"reply_markup",
|
||||
"reply_to_message_id",
|
||||
"allow_sending_without_reply",
|
||||
"message_thread_id",
|
||||
},
|
||||
"video_note": {
|
||||
"duration",
|
||||
"length",
|
||||
"thumbnail",
|
||||
"disable_notification",
|
||||
"protect_content",
|
||||
"reply_markup",
|
||||
"reply_to_message_id",
|
||||
"allow_sending_without_reply",
|
||||
"message_thread_id",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +147,7 @@ def get_message_content(message: types.Message) -> MessageContent:
|
||||
Использует match/case вместо длинных if-elif цепочек.
|
||||
"""
|
||||
text = (message.text or message.caption or "").strip()
|
||||
|
||||
|
||||
# Проверяем наличие медиа-контента
|
||||
media_content = None
|
||||
if message.photo:
|
||||
@@ -58,7 +166,7 @@ def get_message_content(message: types.Message) -> MessageContent:
|
||||
media_content = ("sticker", message.sticker.file_id)
|
||||
elif message.video_note:
|
||||
media_content = ("video_note", message.video_note.file_id)
|
||||
|
||||
|
||||
# Используем match/case для определения типа контента
|
||||
match media_content:
|
||||
case (content_type, file_id):
|
||||
@@ -77,79 +185,69 @@ async def send_message_by_type(bot, chat_id: int, content: MessageContent, **kwa
|
||||
"""
|
||||
# Фильтруем kwargs для данного типа сообщения
|
||||
filtered_kwargs = filter_kwargs(content.content_type, kwargs)
|
||||
|
||||
|
||||
match content.content_type:
|
||||
case "text":
|
||||
await bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=content.text,
|
||||
**filtered_kwargs
|
||||
)
|
||||
await bot.send_message(chat_id=chat_id, text=content.text, **filtered_kwargs)
|
||||
case "photo":
|
||||
await bot.send_photo(
|
||||
chat_id=chat_id,
|
||||
photo=content.file_id,
|
||||
caption=content.text or None,
|
||||
**filtered_kwargs
|
||||
**filtered_kwargs,
|
||||
)
|
||||
case "video":
|
||||
await bot.send_video(
|
||||
chat_id=chat_id,
|
||||
video=content.file_id,
|
||||
caption=content.text or None,
|
||||
**filtered_kwargs
|
||||
**filtered_kwargs,
|
||||
)
|
||||
case "animation":
|
||||
await bot.send_animation(
|
||||
chat_id=chat_id,
|
||||
animation=content.file_id,
|
||||
caption=content.text or None,
|
||||
**filtered_kwargs
|
||||
**filtered_kwargs,
|
||||
)
|
||||
case "document":
|
||||
await bot.send_document(
|
||||
chat_id=chat_id,
|
||||
document=content.file_id,
|
||||
caption=content.text or None,
|
||||
**filtered_kwargs
|
||||
**filtered_kwargs,
|
||||
)
|
||||
case "audio":
|
||||
await bot.send_audio(
|
||||
chat_id=chat_id,
|
||||
audio=content.file_id,
|
||||
caption=content.text or None,
|
||||
**filtered_kwargs
|
||||
**filtered_kwargs,
|
||||
)
|
||||
case "voice":
|
||||
await bot.send_voice(
|
||||
chat_id=chat_id,
|
||||
voice=content.file_id,
|
||||
caption=content.text or None,
|
||||
**filtered_kwargs
|
||||
**filtered_kwargs,
|
||||
)
|
||||
case "sticker":
|
||||
await bot.send_sticker(
|
||||
chat_id=chat_id,
|
||||
sticker=content.file_id,
|
||||
**filtered_kwargs
|
||||
)
|
||||
await bot.send_sticker(chat_id=chat_id, sticker=content.file_id, **filtered_kwargs)
|
||||
case "video_note":
|
||||
await bot.send_video_note(
|
||||
chat_id=chat_id,
|
||||
video_note=content.file_id,
|
||||
**filtered_kwargs
|
||||
chat_id=chat_id, video_note=content.file_id, **filtered_kwargs
|
||||
)
|
||||
case _:
|
||||
# Fallback для неизвестных типов - отправляем как текст
|
||||
text_kwargs = filter_kwargs("text", kwargs)
|
||||
await bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=content.text or "Unknown content type",
|
||||
**text_kwargs
|
||||
chat_id=chat_id, text=content.text or "Unknown content type", **text_kwargs
|
||||
)
|
||||
|
||||
|
||||
async def send_message_via_queue(queue_manager, uid: int, content: MessageContent, **kwargs) -> None:
|
||||
async def send_message_via_queue(
|
||||
queue_manager, uid: int, content: MessageContent, **kwargs
|
||||
) -> None:
|
||||
"""
|
||||
Отправляет сообщение через очередь в зависимости от типа контента.
|
||||
Использует match/case вместо длинных if-elif цепочек.
|
||||
@@ -157,12 +255,10 @@ async def send_message_via_queue(queue_manager, uid: int, content: MessageConten
|
||||
"""
|
||||
# Фильтруем kwargs для данного типа сообщения
|
||||
filtered_kwargs = filter_kwargs(content.content_type, kwargs)
|
||||
|
||||
|
||||
match content.content_type:
|
||||
case "text":
|
||||
await queue_manager.send_message(
|
||||
chat_id=uid, text=content.text, **filtered_kwargs
|
||||
)
|
||||
await queue_manager.send_message(chat_id=uid, text=content.text, **filtered_kwargs)
|
||||
case "photo":
|
||||
await queue_manager.send_photo(
|
||||
chat_id=uid, photo=content.file_id, caption=content.text or None, **filtered_kwargs
|
||||
@@ -173,11 +269,17 @@ async def send_message_via_queue(queue_manager, uid: int, content: MessageConten
|
||||
)
|
||||
case "animation":
|
||||
await queue_manager.send_animation(
|
||||
chat_id=uid, animation=content.file_id, caption=content.text or None, **filtered_kwargs
|
||||
chat_id=uid,
|
||||
animation=content.file_id,
|
||||
caption=content.text or None,
|
||||
**filtered_kwargs,
|
||||
)
|
||||
case "document":
|
||||
await queue_manager.send_document(
|
||||
chat_id=uid, document=content.file_id, caption=content.text or None, **filtered_kwargs
|
||||
chat_id=uid,
|
||||
document=content.file_id,
|
||||
caption=content.text or None,
|
||||
**filtered_kwargs,
|
||||
)
|
||||
case "audio":
|
||||
await queue_manager.send_audio(
|
||||
@@ -203,7 +305,9 @@ async def send_message_via_queue(queue_manager, uid: int, content: MessageConten
|
||||
)
|
||||
|
||||
|
||||
async def send_direct_message(bot, chat_id: int, content: MessageContent, extra_text: str = "", **kwargs) -> None:
|
||||
async def send_direct_message(
|
||||
bot, chat_id: int, content: MessageContent, extra_text: str = "", **kwargs
|
||||
) -> None:
|
||||
"""
|
||||
Отправляет прямое сообщение с дополнительной обработкой для sticker и video_note.
|
||||
Для этих типов медиа отправляется отдельное текстовое сообщение, т.к. они не поддерживают caption.
|
||||
@@ -213,51 +317,34 @@ async def send_direct_message(bot, chat_id: int, content: MessageContent, extra_
|
||||
case "sticker":
|
||||
# Отправляем стикер с отфильтрованными параметрами
|
||||
sticker_kwargs = filter_kwargs("sticker", kwargs)
|
||||
await bot.send_sticker(
|
||||
chat_id=chat_id,
|
||||
sticker=content.file_id,
|
||||
**sticker_kwargs
|
||||
)
|
||||
await bot.send_sticker(chat_id=chat_id, sticker=content.file_id, **sticker_kwargs)
|
||||
# Если есть текст с подписью, отправляем отдельно
|
||||
if content.text or extra_text:
|
||||
text_to_send = (content.text + extra_text) if content.text else extra_text
|
||||
text_kwargs = filter_kwargs("text", kwargs)
|
||||
await bot.send_message(
|
||||
chat_id,
|
||||
text_to_send,
|
||||
**text_kwargs
|
||||
)
|
||||
await bot.send_message(chat_id, text_to_send, **text_kwargs)
|
||||
case "video_note":
|
||||
# Отправляем видео-заметку с отфильтрованными параметрами
|
||||
video_note_kwargs = filter_kwargs("video_note", kwargs)
|
||||
await bot.send_video_note(
|
||||
chat_id=chat_id,
|
||||
video_note=content.file_id,
|
||||
**video_note_kwargs
|
||||
chat_id=chat_id, video_note=content.file_id, **video_note_kwargs
|
||||
)
|
||||
# Если есть текст с подписью, отправляем отдельно
|
||||
if content.text or extra_text:
|
||||
text_to_send = (content.text + extra_text) if content.text else extra_text
|
||||
text_kwargs = filter_kwargs("text", kwargs)
|
||||
await bot.send_message(
|
||||
chat_id,
|
||||
text_to_send,
|
||||
**text_kwargs
|
||||
)
|
||||
await bot.send_message(chat_id, text_to_send, **text_kwargs)
|
||||
case "text":
|
||||
# Для текста объединяем с extra_text
|
||||
final_text = (content.text + extra_text) if content.text else extra_text
|
||||
text_kwargs = filter_kwargs("text", kwargs)
|
||||
await bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=final_text,
|
||||
**text_kwargs
|
||||
)
|
||||
await bot.send_message(chat_id=chat_id, text=final_text, **text_kwargs)
|
||||
case _:
|
||||
# Для остальных типов медиа используем caption
|
||||
final_caption = (content.text + extra_text) if content.text else None
|
||||
await send_message_by_type(
|
||||
bot, chat_id,
|
||||
bot,
|
||||
chat_id,
|
||||
MessageContent(content.content_type, content.file_id, final_caption),
|
||||
**kwargs
|
||||
)
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@ from typing import Any
|
||||
from aiogram.exceptions import TelegramAPIError, TelegramBadRequest
|
||||
from aiogram.types import CallbackQuery
|
||||
|
||||
|
||||
_EXPIRED_CALLBACK_MARKERS = (
|
||||
"query is too old",
|
||||
"response timeout expired",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import logging
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from config.settings import Settings
|
||||
from bot.services.panel_api_service import PanelApiService
|
||||
from config.settings import Settings
|
||||
|
||||
|
||||
async def _encrypt_raw_link(settings: Settings, raw_link: str) -> Optional[str]:
|
||||
@@ -14,7 +14,9 @@ async def _encrypt_raw_link(settings: Settings, raw_link: str) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
async def prepare_config_links(settings: Settings, raw_link: Optional[str]) -> Tuple[Optional[str], Optional[str]]:
|
||||
async def prepare_config_links(
|
||||
settings: Settings, raw_link: Optional[str]
|
||||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
"""
|
||||
Build the user-facing connection key and the URL for the connect button.
|
||||
|
||||
@@ -38,7 +40,9 @@ async def prepare_config_links(settings: Settings, raw_link: Optional[str]) -> T
|
||||
display_link = encrypted_payload
|
||||
button_link = display_link
|
||||
else:
|
||||
logging.error("CRYPT4_ENABLED is set but encryption failed; using raw link as fallback.")
|
||||
logging.error(
|
||||
"CRYPT4_ENABLED is set but encryption failed; using raw link as fallback."
|
||||
)
|
||||
|
||||
redirect_base = (settings.CRYPT4_REDIRECT_URL or "").strip()
|
||||
if redirect_base and settings.CRYPT4_ENABLED and display_link:
|
||||
|
||||
@@ -34,5 +34,3 @@ def month_start(base_dt: Optional[datetime] = None) -> datetime:
|
||||
else:
|
||||
moment = moment.astimezone(timezone.utc)
|
||||
return datetime(moment.year, moment.month, 1, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
|
||||
+47
-85
@@ -1,9 +1,10 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any, Callable, Awaitable, Optional
|
||||
from collections import deque
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from collections import deque
|
||||
from typing import Any, Awaitable, Callable, Dict, Optional
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.exceptions import TelegramBadRequest
|
||||
|
||||
@@ -16,6 +17,7 @@ from bot.utils.telegram_markup import (
|
||||
@dataclass
|
||||
class QueuedMessage:
|
||||
"""Represents a queued message with all necessary parameters"""
|
||||
|
||||
chat_id: int
|
||||
method_name: str # 'send_message', 'edit_message_text', etc.
|
||||
kwargs: Dict[str, Any]
|
||||
@@ -24,7 +26,7 @@ class QueuedMessage:
|
||||
|
||||
class MessageQueue:
|
||||
"""Message queue with rate limiting for Telegram API"""
|
||||
|
||||
|
||||
def __init__(self, messages_per_second: float, burst_size: int = 5):
|
||||
self.messages_per_second = messages_per_second
|
||||
self.burst_size = burst_size
|
||||
@@ -34,31 +36,31 @@ class MessageQueue:
|
||||
self.delay_between_messages = 1.0 / messages_per_second
|
||||
self.total_sent = 0
|
||||
self.total_failed = 0
|
||||
|
||||
|
||||
async def add_message(self, message: QueuedMessage) -> None:
|
||||
"""Add message to queue"""
|
||||
self.queue.append(message)
|
||||
if not self.is_processing:
|
||||
asyncio.create_task(self._process_queue())
|
||||
|
||||
|
||||
async def _process_queue(self) -> None:
|
||||
"""Process messages from queue with rate limiting"""
|
||||
if self.is_processing:
|
||||
return
|
||||
|
||||
|
||||
self.is_processing = True
|
||||
|
||||
|
||||
try:
|
||||
while self.queue:
|
||||
# Check if we need to wait
|
||||
await self._wait_if_needed()
|
||||
|
||||
|
||||
# Get and process next message
|
||||
message = self.queue.popleft()
|
||||
try:
|
||||
await self._send_message(message)
|
||||
self._record_send_time()
|
||||
|
||||
|
||||
except TelegramBadRequest as exc:
|
||||
fallback_message = self._build_profile_link_fallback(message, exc)
|
||||
if fallback_message:
|
||||
@@ -78,25 +80,25 @@ class MessageQueue:
|
||||
f"Failed to send fallback message to {message.chat_id}: {retry_exc}"
|
||||
)
|
||||
continue
|
||||
|
||||
|
||||
self.total_failed += 1
|
||||
logging.error(f"Failed to send queued message to {message.chat_id}: {exc}")
|
||||
|
||||
|
||||
except Exception:
|
||||
self.total_failed += 1
|
||||
logging.exception("Failed to send queued message to %s.", message.chat_id)
|
||||
|
||||
|
||||
finally:
|
||||
self.is_processing = False
|
||||
|
||||
|
||||
async def _wait_if_needed(self) -> None:
|
||||
"""Wait if we need to respect rate limits"""
|
||||
if not self.last_send_times:
|
||||
return
|
||||
|
||||
|
||||
# Calculate time since last message
|
||||
time_since_last = (datetime.now() - self.last_send_times[-1]).total_seconds()
|
||||
|
||||
|
||||
if time_since_last < self.delay_between_messages:
|
||||
wait_time = self.delay_between_messages - time_since_last
|
||||
await asyncio.sleep(wait_time)
|
||||
@@ -132,7 +134,7 @@ class MessageQueue:
|
||||
kwargs=fallback_kwargs,
|
||||
callback=message.callback,
|
||||
)
|
||||
|
||||
|
||||
async def _send_message(self, message: QueuedMessage) -> Any:
|
||||
"""Send a single message - to be implemented by subclass"""
|
||||
raise NotImplementedError("Subclass must implement _send_message")
|
||||
@@ -140,150 +142,110 @@ class MessageQueue:
|
||||
|
||||
class TelegramMessageQueue(MessageQueue):
|
||||
"""Telegram-specific message queue"""
|
||||
|
||||
|
||||
def __init__(self, bot: Bot, messages_per_second: float, burst_size: int = 5):
|
||||
super().__init__(messages_per_second, burst_size)
|
||||
self.bot = bot
|
||||
|
||||
|
||||
async def _send_message(self, message: QueuedMessage) -> Any:
|
||||
"""Send message using bot method"""
|
||||
method = getattr(self.bot, message.method_name)
|
||||
result = await method(chat_id=message.chat_id, **message.kwargs)
|
||||
|
||||
|
||||
# Call callback if provided
|
||||
if message.callback:
|
||||
await message.callback(result)
|
||||
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class MessageQueueManager:
|
||||
"""Manager for different types of message queues"""
|
||||
|
||||
|
||||
def __init__(self, bot: Bot):
|
||||
self.bot = bot
|
||||
|
||||
|
||||
# Different queues for different types of chats
|
||||
self.group_queue = TelegramMessageQueue(
|
||||
bot=bot,
|
||||
messages_per_second=15/60, # 15 messages per minute for groups
|
||||
burst_size=3
|
||||
messages_per_second=15 / 60, # 15 messages per minute for groups
|
||||
burst_size=3,
|
||||
)
|
||||
|
||||
|
||||
self.user_queue = TelegramMessageQueue(
|
||||
bot=bot,
|
||||
bot=bot,
|
||||
messages_per_second=25, # 25 messages per second for users
|
||||
burst_size=10
|
||||
burst_size=10,
|
||||
)
|
||||
|
||||
|
||||
def _is_group_chat(self, chat_id: int) -> bool:
|
||||
"""Check if chat_id belongs to a group or channel"""
|
||||
return str(chat_id).startswith('-100')
|
||||
|
||||
return str(chat_id).startswith("-100")
|
||||
|
||||
async def send_message(self, chat_id: int, **kwargs) -> None:
|
||||
"""Queue a send_message call"""
|
||||
queue = self.group_queue if self._is_group_chat(chat_id) else self.user_queue
|
||||
message = QueuedMessage(
|
||||
chat_id=chat_id,
|
||||
method_name='send_message',
|
||||
kwargs=kwargs
|
||||
)
|
||||
message = QueuedMessage(chat_id=chat_id, method_name="send_message", kwargs=kwargs)
|
||||
await queue.add_message(message)
|
||||
|
||||
|
||||
async def edit_message_text(self, chat_id: int, **kwargs) -> None:
|
||||
"""Queue an edit_message_text call"""
|
||||
queue = self.group_queue if self._is_group_chat(chat_id) else self.user_queue
|
||||
message = QueuedMessage(
|
||||
chat_id=chat_id,
|
||||
method_name='edit_message_text',
|
||||
kwargs=kwargs
|
||||
)
|
||||
message = QueuedMessage(chat_id=chat_id, method_name="edit_message_text", kwargs=kwargs)
|
||||
await queue.add_message(message)
|
||||
|
||||
|
||||
async def send_document(self, chat_id: int, **kwargs) -> None:
|
||||
"""Queue a send_document call"""
|
||||
queue = self.group_queue if self._is_group_chat(chat_id) else self.user_queue
|
||||
message = QueuedMessage(
|
||||
chat_id=chat_id,
|
||||
method_name='send_document',
|
||||
kwargs=kwargs
|
||||
)
|
||||
message = QueuedMessage(chat_id=chat_id, method_name="send_document", kwargs=kwargs)
|
||||
await queue.add_message(message)
|
||||
|
||||
|
||||
async def send_photo(self, chat_id: int, **kwargs) -> None:
|
||||
"""Queue a send_photo call"""
|
||||
queue = self.group_queue if self._is_group_chat(chat_id) else self.user_queue
|
||||
message = QueuedMessage(
|
||||
chat_id=chat_id,
|
||||
method_name='send_photo',
|
||||
kwargs=kwargs
|
||||
)
|
||||
message = QueuedMessage(chat_id=chat_id, method_name="send_photo", kwargs=kwargs)
|
||||
await queue.add_message(message)
|
||||
|
||||
async def send_video(self, chat_id: int, **kwargs) -> None:
|
||||
"""Queue a send_video call"""
|
||||
queue = self.group_queue if self._is_group_chat(chat_id) else self.user_queue
|
||||
message = QueuedMessage(
|
||||
chat_id=chat_id,
|
||||
method_name='send_video',
|
||||
kwargs=kwargs
|
||||
)
|
||||
message = QueuedMessage(chat_id=chat_id, method_name="send_video", kwargs=kwargs)
|
||||
await queue.add_message(message)
|
||||
|
||||
async def send_animation(self, chat_id: int, **kwargs) -> None:
|
||||
"""Queue a send_animation (GIF) call"""
|
||||
queue = self.group_queue if self._is_group_chat(chat_id) else self.user_queue
|
||||
message = QueuedMessage(
|
||||
chat_id=chat_id,
|
||||
method_name='send_animation',
|
||||
kwargs=kwargs
|
||||
)
|
||||
message = QueuedMessage(chat_id=chat_id, method_name="send_animation", kwargs=kwargs)
|
||||
await queue.add_message(message)
|
||||
|
||||
async def send_audio(self, chat_id: int, **kwargs) -> None:
|
||||
"""Queue a send_audio call"""
|
||||
queue = self.group_queue if self._is_group_chat(chat_id) else self.user_queue
|
||||
message = QueuedMessage(
|
||||
chat_id=chat_id,
|
||||
method_name='send_audio',
|
||||
kwargs=kwargs
|
||||
)
|
||||
message = QueuedMessage(chat_id=chat_id, method_name="send_audio", kwargs=kwargs)
|
||||
await queue.add_message(message)
|
||||
|
||||
async def send_voice(self, chat_id: int, **kwargs) -> None:
|
||||
"""Queue a send_voice call"""
|
||||
queue = self.group_queue if self._is_group_chat(chat_id) else self.user_queue
|
||||
message = QueuedMessage(
|
||||
chat_id=chat_id,
|
||||
method_name='send_voice',
|
||||
kwargs=kwargs
|
||||
)
|
||||
message = QueuedMessage(chat_id=chat_id, method_name="send_voice", kwargs=kwargs)
|
||||
await queue.add_message(message)
|
||||
|
||||
async def send_sticker(self, chat_id: int, **kwargs) -> None:
|
||||
"""Queue a send_sticker call"""
|
||||
queue = self.group_queue if self._is_group_chat(chat_id) else self.user_queue
|
||||
message = QueuedMessage(
|
||||
chat_id=chat_id,
|
||||
method_name='send_sticker',
|
||||
kwargs=kwargs
|
||||
)
|
||||
message = QueuedMessage(chat_id=chat_id, method_name="send_sticker", kwargs=kwargs)
|
||||
await queue.add_message(message)
|
||||
|
||||
async def send_video_note(self, chat_id: int, **kwargs) -> None:
|
||||
"""Queue a send_video_note call"""
|
||||
queue = self.group_queue if self._is_group_chat(chat_id) else self.user_queue
|
||||
message = QueuedMessage(
|
||||
chat_id=chat_id,
|
||||
method_name='send_video_note',
|
||||
kwargs=kwargs
|
||||
)
|
||||
message = QueuedMessage(chat_id=chat_id, method_name="send_video_note", kwargs=kwargs)
|
||||
await queue.add_message(message)
|
||||
|
||||
|
||||
async def answer_callback_query(self, callback_query_id: str, **kwargs) -> None:
|
||||
"""Send callback query answer immediately (not rate limited)"""
|
||||
await self.bot.answer_callback_query(callback_query_id, **kwargs)
|
||||
|
||||
|
||||
def get_queue_stats(self) -> Dict[str, Any]:
|
||||
"""Get statistics about queues"""
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Helpers for Telegram Mini App URLs (subscription webapp)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
@@ -64,7 +64,9 @@ def request_client_ip(
|
||||
return forwarded_ip
|
||||
|
||||
|
||||
def ip_in_allowlist(ip_value: Optional[str], allowed_entries: Optional[Sequence[str] | str]) -> bool:
|
||||
def ip_in_allowlist(
|
||||
ip_value: Optional[str], allowed_entries: Optional[Sequence[str] | str]
|
||||
) -> bool:
|
||||
parsed_ip = _parse_ip(ip_value)
|
||||
if parsed_ip is None:
|
||||
return False
|
||||
|
||||
@@ -20,8 +20,7 @@ def remove_profile_link_buttons(
|
||||
button
|
||||
for button in row
|
||||
if not (
|
||||
getattr(button, "url", None)
|
||||
and str(button.url).startswith(TG_USER_LINK_PREFIX)
|
||||
getattr(button, "url", None) and str(button.url).startswith(TG_USER_LINK_PREFIX)
|
||||
)
|
||||
]
|
||||
if filtered_row:
|
||||
|
||||
@@ -153,9 +153,7 @@ def _normalize_for_detection(value: str) -> str:
|
||||
normalized = unicodedata.normalize("NFKD", value)
|
||||
normalized = normalized.translate(_PRE_LOWER_TRANSLATION)
|
||||
normalized = normalized.lower()
|
||||
normalized = "".join(
|
||||
ch for ch in normalized if unicodedata.category(ch) != "Mn"
|
||||
)
|
||||
normalized = "".join(ch for ch in normalized if unicodedata.category(ch) != "Mn")
|
||||
normalized = normalized.translate(_POST_LOWER_TRANSLATION)
|
||||
normalized = normalized.replace("rn", "m")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user