# This file is part of SenkoGuardianModules
# Copyright (c) 2025 Senko
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# meta developer: @SenkoGuardianModules
import asyncio
import random
import re
from .. import loader, utils
from herokutl.tl.functions.payments import GetSavedStarGiftsRequest
from herokutl.tl.functions.channels import GetFullChannelRequest
from herokutl.tl.types import Message, StarGiftUnique, Channel
from herokutl.errors.rpcerrorlist import DocumentInvalidError, FloodWaitError, ChatAdminRequiredError
from telethon.utils import get_display_name
@loader.tds
class GiftFinderMod(loader.Module):
"""Парсер пользователей с NFT-подарками в чате."""
strings = {
"name": "GiftFinder",
"not_a_chat": "🚫 Не удалось найти указанный чат.",
"scanning": "⏳ Сканирую участников...",
"scanning_supplement": "⏳ Список участников неполон. Дополнительно сканирую сообщения...",
"scanning_messages_only": "⏳ Участники скрыты. Сканирую только сообщения...",
"header": "🔖 Те у кого есть НФТ подарки:",
"premium_star": "⭐️",
"flood_wait": "\n😖 Поймал FloodWait на {} секунд. Увеличиваю задержку и жду...",
"scanning_safe": "⏳ Сканирую участников...",
"scanning_supplement_safe": "⏳ Список участников неполон. Дополнительно сканирую сообщения...",
"scanning_messages_only_safe": "⏳ Участники скрыты. Сканирую только сообщения...",
"flood_wait_safe": "\n😖 Поймал FloodWait на {} секунд. Увеличиваю задержку и жду...",
"no_users_found": "🚫 В этом чате не найдено пользователей с NFT-подарками.",
}
async def _safe_edit(self, msg: Message, text_premium: str, text_safe: str):
try:
await msg.edit(text_premium)
except DocumentInvalidError:
await msg.edit(text_safe)
except Exception:
pass
async def giftscancmd(self, message: Message):
"""
Ищет пользователей с NFT-подарками в чате.
Использование: .giftscan [лимит] или .giftscan [ID чата] [лимит]
"""
args = utils.get_args_raw(message)
chat_arg = None
msgs_limit = 3000
if args:
parts = args.split()
first_arg = parts[0]
if first_arg.lstrip('-').isdigit():
chat_arg = int(first_arg)
if len(parts) > 1 and parts[1].isdigit():
msgs_limit = int(parts[1])
else:
chat_arg = first_arg
if len(parts) > 1 and parts[1].isdigit():
msgs_limit = int(parts[1])
if not chat_arg and args and args.isdigit():
msgs_limit = int(args)
try:
msg = await utils.answer(message, self.strings("scanning"))
except DocumentInvalidError:
msg = await utils.answer(message, self.strings("scanning_safe"))
try:
chat = await self.client.get_entity(chat_arg) if chat_arg is not None else await message.get_chat()
except Exception:
await self._safe_edit(msg, self.strings("not_a_chat"), self.strings("not_a_chat"))
return
user_ids = set()
scan_messages_mode = False
try:
if isinstance(chat, Channel):
full_chat = await self.client(GetFullChannelRequest(channel=chat))
total_participants = full_chat.full_chat.participants_count
else:
total_participants = chat.participants_count
participants = await self.client.get_participants(chat, limit=None)
user_ids.update(user.id for user in participants)
if len(participants) < total_participants:
scan_messages_mode = True
await self._safe_edit(msg, self.strings("scanning_supplement"), self.strings("scanning_supplement_safe"))
except (ChatAdminRequiredError, AttributeError, TypeError, ValueError):
scan_messages_mode = True
await self._safe_edit(msg, self.strings("scanning_messages_only"), self.strings("scanning_messages_only_safe"))
if scan_messages_mode:
async for m in self.client.iter_messages(chat, limit=msgs_limit):
if m.from_id and hasattr(m.from_id, 'user_id'):
user_ids.add(m.from_id.user_id)
found_users = []
base_delay_min, base_delay_max, flood_penalty = 0.5, 1.5, 0.0
for user_id in user_ids:
try:
user = await self.client.get_entity(user_id)
if user.bot or user.is_self: continue
except Exception: continue
await asyncio.sleep(random.uniform(base_delay_min + flood_penalty, base_delay_max + flood_penalty))
while True:
try:
all_gifts = await self.client(GetSavedStarGiftsRequest(peer=user, offset="", limit=100))
if gifts := [g for g in all_gifts.gifts if isinstance(g.gift, StarGiftUnique)]:
raw_name = get_display_name(user)
s_name = re.sub(r'[\u2066-\u2069\u200e\u200f\u202a-\u202e\u3164\u115f\u2800]', '', raw_name).strip()
link_text = f"@{user.username}" if not s_name and user.username else (f"User ID: {user.id}" if not s_name else utils.escape_html(s_name))
link = f'{link_text}' if user.username else f'{link_text}'
p_icon = self.strings('premium_star') if getattr(user, 'premium', False) else ""
found_users.append(f"• {p_icon} {link} - {len(gifts)}")
break
except FloodWaitError as e:
current_text = (await self.client.get_messages(msg.chat_id, ids=msg.id)).text
premium_text = current_text + self.strings("flood_wait").format(e.seconds)
safe_text = current_text + self.strings("flood_wait_safe").format(e.seconds)
await self._safe_edit(msg, premium_text, safe_text)
flood_penalty += 0.2
await asyncio.sleep(e.seconds)
continue
except Exception: break
if not found_users:
await self._safe_edit(msg, self.strings("no_users_found"), self.strings("no_users_found"))
return
user_list = "\n".join(found_users)
response_text = f"{self.strings('header')}\n
{user_list}
"
safe_header = "🔖 " + self.strings("header").split("")[1]
safe_list = [line.replace(self.strings("premium_star"), "⭐️") for line in found_users]
safe_user_list = '\n'.join(safe_list)
response_text_safe = f"{safe_header}\n{safe_user_list}
"
await self._safe_edit(msg, response_text, response_text_safe)
# горе кодер