feat(cli): add Worker and main for TDATA check

This commit is contained in:
2026-08-02 00:18:40 +05:00
parent b5bc66bbf4
commit a74e2eb328
4 changed files with 267 additions and 98 deletions
+33
View File
@@ -0,0 +1,33 @@
import asyncio
import logging
from modules.thread import Worker
logger = logging.getLogger(__name__)
async def main():
async with Worker() as worker:
await worker.check_all()
if __name__ == "__main__":
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)-7s | %(message)s",
datefmt="%H:%M:%S",
handlers=[logging.StreamHandler()],
)
logging.getLogger("telethon").setLevel(logging.WARNING)
logging.getLogger("opentele2").setLevel(logging.WARNING)
try:
asyncio.run(main())
except KeyboardInterrupt:
logger.info("Прервано пользователем")
except Exception as e:
logger.exception(f"Критическая ошибка: {e}")
raise
+77 -95
View File
@@ -14,15 +14,17 @@ from modules.tdata import TDATA
logger = logging.getLogger(__name__)
class SessionInvalid(Exception):
pass
# Unified Result class
@dataclass
class Result:
valid: bool
error: Optional[str] = None
me: Optional[User] = None
stars: Optional[int] = None
country: Optional[str] = None
contacts: Optional[List[User]] = None
chats: Optional[List[Chat]] = None
channels: Optional[List[Channel]] = None
@@ -41,6 +43,7 @@ class Checker:
self.desktop = None
async def __aenter__(self):
self.__ensure_client()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
@@ -48,110 +51,94 @@ class Checker:
logger.debug("Отключение клиента Telethon")
try:
await self.client.disconnect()
except:
pass
async def _ensure_client(self):
"""Обеспечение инициализации клиента перед операцией"""
except Exception as e:
logger.error(f"Ошибка отключения клиента Telethon: {e}")
async def __ensure_client(self):
if self.client is None:
if self.desktop is None:
raise Exception("TDATA не инициализирована")
logger.debug("Инициализация клиента Telethon из TDATA")
self.client = await self.desktop.convert()
async def validate(self) -> Result:
"""Валидация TDATA"""
async def check_stars(self, me: User) -> int:
try:
if self.desktop is None:
return Result(valid=False, error="TDATA не инициализирована")
if not await self.desktop.validate():
return Result(valid=False, error="TDATA невалидна или пуста")
await self._ensure_client()
me = await self.client.get_me()
if not me:
return Result(
valid=False, error="Не удалось получить информацию о пользователе"
)
return Result(valid=True, me=me)
stars_status = await self.client(GetStarsStatusRequest(peer=me))
return stars_status.balance.amount
except Exception as e:
logger.error(f"Ошибка валидации: {e}")
return Result(valid=False, error=str(e))
logger.warning(f"Не удалось получить баланс звезд: {e}")
return 0
async def check_country(self, me: User) -> str:
try:
phone_parse = parse(str(f"+{me.phone}"))
return geocoder.description_for_number(phone_parse, "en")
except Exception as e:
logger.warning(f"Не удалось определить страну: {e}")
return "N/A"
async def check_contacts(self) -> List[User]:
try:
logger.debug("Получение контактов")
result = await self.client(GetContactsRequest(hash=0))
return result.users
except Exception as e:
logger.warning(f"Не удалось получить контакты: {e}")
async def check_chats(self) -> List[Chat]:
try:
logger.debug("Получение всех диалогов")
chats = []
async for dialog in self.client.iter_dialogs():
chats.append(dialog.entity)
return chats
except Exception as e:
logger.warning(f"Не удалось получить чаты: {e}")
async def check_channels(self, chats: List[Chat]) -> List[Channel]:
try:
channels = [chat for chat in chats if isinstance(chat, Channel)]
return channels
except Exception as e:
logger.warning(f"Не удалось отфильтровать каналы: {e}")
async def check_admin_channels(self, channels: List[Channel]) -> List[Channel]:
try:
logger.debug("Проверка прав администратора")
admin_channels = []
for channel in channels:
permissions = await self.client.get_permissions(channel, "me")
if permissions.is_admin or permissions.is_creator:
admin_channels.append(channel)
except Exception as e:
logger.warning(f"Не удалось проверить админские права: {e}")
async def check_all(self) -> Result:
"""Полная проверка аккаунта"""
try:
# Сначала валидация
validation = await self.validate()
if not validation.valid:
return validation
me = validation.me
logger.debug(f"Пользователь: {me.first_name} (+{me.phone})")
me = await self.client.get_me()
# Собираем данные с обработкой ошибок
stars = None
country = None
contacts = None
chats = None
channels = None
admin_channels = None
stars = self.check_stars(me=me)
country = self.check_country(me=me)
try:
stars_status = await self.client(GetStarsStatusRequest(peer=me))
stars = stars_status.balance.amount
except Exception as e:
logger.warning(f"Не удалось получить баланс звезд: {e}")
try:
phone_parse = parse(str(f"+{me.phone}"))
country = geocoder.description_for_number(phone_parse, "ru")
except Exception as e:
logger.warning(f"Не удалось определить страну: {e}")
try:
logger.debug("Получение контактов")
result = await self.client(GetContactsRequest(hash=0))
contacts = result.users
except Exception as e:
logger.warning(f"Не удалось получить контакты: {e}")
try:
logger.debug("Получение всех диалогов")
chats = []
async for dialog in self.client.iter_dialogs():
chats.append(dialog.entity)
except Exception as e:
logger.warning(f"Не удалось получить чаты: {e}")
if chats:
try:
channels = [chat for chat in chats if isinstance(chat, Channel)]
except Exception as e:
logger.warning(f"Не удалось отфильтровать каналы: {e}")
try:
logger.debug("Проверка прав администратора")
admin_channels = []
for chat in chats:
if isinstance(chat, Channel):
try:
permissions = await self.client.get_permissions(
chat, "me"
)
if permissions.is_admin or permissions.is_creator:
admin_channels.append(chat)
except:
pass
except Exception as e:
logger.warning(f"Не удалось проверить админские права: {e}")
contacts = self.check_contacts()
chats = self.check_chats()
channels = [] if chats == [] else self.check_channels(chats=chats)
admin_channels = (
[] if channels == [] else self.check_admin_channels(channels=channels)
)
return Result(
valid=True,
me=me,
stars=stars,
country=country,
@@ -163,9 +150,4 @@ class Checker:
except Exception as e:
logger.error(f"Критическая ошибка при проверке: {e}")
return Result(valid=False, error=str(e))
class MassChecker:
def __init__(self):
pass
raise SessionInvalid()
+5 -3
View File
@@ -16,10 +16,12 @@ logger = logging.getLogger(__name__)
class TDATA:
def __init__(self, path: str = "tdata"):
def __init__(self, path: Path, temp_path: Path):
try:
self.path = path
if not Path(self.path).exists():
self.temp_path = temp_path
if not self.path.exists():
logger.error(f"Путь '{self.path}' не существует")
raise FileNotFoundError(f"Путь '{self.path}' не существует")
@@ -39,7 +41,7 @@ class TDATA:
if self.desktop:
return self.desktop
return TDesktop(self.path, api=self.api)
return TDesktop(self.path.absolute(), api=self.api)
except TFileNotFound as e:
logger.warning(f"Файлы TDATA не найдены или неполные: {e}")
+152
View File
@@ -0,0 +1,152 @@
import os
import logging
from shutil import move
from pathlib import Path
from countryflag import getflag
from checker import Result, Checker, SessionInvalid
logger = logging.getLogger(__name__)
class Worker:
def __init__(
self,
input_path: str = "tdata",
output_path: str = "data",
temp_path: str = "temp",
):
self.input_path = Path(input_path).absolute()
self.output_path = Path(output_path).absolute()
self.temp_path = Path(temp_path).absolute()
self.valid_path = self.output_path.joinpath("valid")
self.invalid_path = self.output_path.joinpath("invalid")
self.all_path = self.valid_path.joinpath("all.txt")
async def __aenter__(self):
if not self.input_path.exists():
self.input_path.mkdir()
if not self.output_path.exists():
self.output_path.mkdir()
if not self.temp_path.exists():
self.temp_path.mkdir()
else:
self.__cleanup()
if not self.valid_path.exists():
self.valid_path.mkdir()
if not self.invalid_path.exists():
self.invalid_path.mkdir()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
self.__cleanup()
if self.client:
logger.debug("Отключение клиента Telethon")
try:
await self.client.disconnect()
except:
pass
def __cleanup(self):
if self.temp_path.exists():
deleted_count = 0
for file in os.listdir(path=self.temp_path):
file_path = self.temp_path.joinpath(str(file))
try:
if file_path.isfile() and ".gitinclude" not in file_path:
file_path.unlink(missing_ok=True)
elif file_path.isdir():
file_path.rmdir()
deleted_count += 1
except Exception as e:
logger.warning(f"Не удалось удалить {file_path}: {e}")
if deleted_count > 0:
logger.debug(f"Удалено {deleted_count} временных файлов")
def __list_tdata(self):
tdatas = []
if self.input_path.exists():
for content in os.listdir(path=self.input_path):
joined = self.input_path.joinpath(content)
if joined.is_dir():
tdatas.append(joined.absolute())
return tdatas
def __template(self, result: Result):
return (
f"👤 [{getflag(countries=result.country)} {result.country}] {result.me.phone}{result.me.id}\n"
f"⭐️ {result.stars}\n"
f"💎 Premium: {'true' if result.me.premium else 'false'}\n"
f"👥 {len(result.chats)}/{len(result.contacts)}\n"
f"🍀 {len(result.admin_channels)}"
)
def __append(self, content: str, path: Path):
with open(file=path, mode="+a", encoding="utf-8") as file:
file.write(content)
def __write(self, content: str, path: Path):
with open(file=path, mode="+w", encoding="utf-8") as file:
file.write(content)
def __save_valid(self, user_id: int, result: Result, tdata: Path):
id_path = self.output_path.joinpath(str(user_id))
if not id_path.absolute().exists():
id_path.mkdir()
content = self.__template(result=result)
id_txt_path = id_path.joinpath(f"{user_id}.txt")
# Append to all.txt
self.__append(content=content, path=self.all_path)
# Write to id.txt
self.__write(content=content, path=id_txt_path)
tdata_old_path = self.input_path.joinpath(tdata).absolute()
tdata_relative_id_path = id_path.joinpath(tdata)
tdata_new_path = self.valid_path.joinpath(tdata_relative_id_path).absolute()
# Move tdata
move(src=tdata_old_path, dst=tdata_new_path)
def __save_invalid(self, tdata: Path):
tdata_old_path = self.input_path.joinpath(tdata).absolute()
tdata_new_path = self.invalid_path.joinpath(tdata).absolute()
# Move tdata
move(src=tdata_old_path, dst=tdata_new_path)
async def check_one(self, tdata: str):
tdata_absolute = Path(tdata).absolute()
tdata_relative = Path(tdata).relative_to(self.input_path)
logger.info(f"Начало проверки TDATA: {tdata_absolute}")
try:
async with Checker(path=tdata_absolute) as checker:
result = await checker.check_all()
return
self.__save_valid(user_id=result.me.id, tdata=tdata_relative)
except SessionInvalid:
self.__save_invalid(tdata=tdata_relative)
except:
pass
async def check_all(self):
tdatas = self.__list_tdata()
for tdata in tdatas:
await self.check_one(tdata=tdata)