From a74e2eb3287bd5a83c132def03ed785ffc1d0e76 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 2 Aug 2026 00:18:40 +0500 Subject: [PATCH] feat(cli): add Worker and main for TDATA check --- main.py | 33 +++++++++ modules/checker.py | 172 ++++++++++++++++++++------------------------- modules/tdata.py | 8 ++- modules/thread.py | 152 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 267 insertions(+), 98 deletions(-) create mode 100644 main.py create mode 100644 modules/thread.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..6b2eaae --- /dev/null +++ b/main.py @@ -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 diff --git a/modules/checker.py b/modules/checker.py index 7cfd345..7612a30 100644 --- a/modules/checker.py +++ b/modules/checker.py @@ -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() diff --git a/modules/tdata.py b/modules/tdata.py index 3706c66..f162757 100644 --- a/modules/tdata.py +++ b/modules/tdata.py @@ -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}") diff --git a/modules/thread.py b/modules/thread.py new file mode 100644 index 0000000..4398d1e --- /dev/null +++ b/modules/thread.py @@ -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)