feat(cli): add Worker and main for TDATA check
This commit is contained in:
@@ -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
@@ -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
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user